---
title: 
  | 
  | Replication Material
  | 
  | Facial Finetuning: Using Pretrained Image Classification Models to Predict Politicians' Success
  |
author: "Asbjoern Lindholm, Christian Hjorth, Julian Schuessler"
date: "Contact: julians@ps.au.dk"
output: 
  bookdown::html_document2:
    theme: cosmo
    highlight: kate
    toc: true
    number_sections: false
    toc_float: 
     collapsed: true
     smooth_scroll: true
    code_folding: hide
---

```{r setup, include = F}
set.seed(123)
```


# Link to Paper

Pre-print: https://osf.io/preprints/socarxiv/w6x42 

# Data 

The following code reproduces the figures and tables in the main body and appendices. To reproduce the results, please visit the PSRM Dataverse to download the data and run this file. Due to the memory requirements for fine-tuning the CNN models and the size of the subsequent models, the model predictions have already been provided in the data. To access the fine-tuned models and the replication code for the fine-tuning, please visit the [Facial-Finetuning](https://github.com/asbjoernlindholm/facial-finetuning) Github repository.

**The files uploaded to the PSRM Dataverse are as follows:**

1. Replication_Material_Facial_Finetuning.Rmd: The code for reproducing the graphs and tables in the body and appendices.

2. Replication_Material_Facial_Finetuning.html: The markdown file with reproduction code rendered to html.

3. Attractiveness_finetune_model.R: Script for fine-tuning attractiveness model.

4. Dominance_finetune_model.R: Script for fine-tuning dominance model.

5. Trustworthiness_finetune_model.R: Script for fine-tuning trustworthiness model.

6. OMI_training_data.RData: This file contains the training, validation, and test data from the [One Million Impressions](https://github.com/jcpeterson/omi) dataset.

7. Annotated.and.predicted.test.data.RData: Test data from the One Million Impressions dataset with annotated and predicted scores.

8. CFD 3.0 Norming Data and Codebook.xlsx: Data from the [Chicago Face Database](https://www.chicagofaces.org).

9. OMI_attribute_means.tab: Data from the One Million Impressions dataset.

10. Candidate_data.RData: Full candidate data with model predictions.

11. KRLS.Attractiveness.Ballot.Position.RData: Results from running KRLS with attractiveness on ballot paper position.

12. KRLS.Attractiveness.Personal.Votes.RData: Results from running KRLS with attractiveness on personal votes.

13. KRLS.Dominance.Ballot.Position.RData: Results from running KRLS with dominance on ballot paper position.

14. KRLS.Dominance.Personal.Votes.RData: Results from running KRLS with dominance on personal votes.

15. KRLS.Trustworthiness.Ballot.Position.RData: Results from running KRLS with trustworthiness on ballot paper position.

16. KRLS.Trustworthiness.Personal.Votes.RData: Results from running KRLS with trustworthiness on personal votes.

17. Images/OMI_Images: This folder contains sample images from the One Million Impressions data.

18. Images/CFD_Images: This folder contains sample images from the Chicago Face Database.

# Load required packages

```{r packages, eval = T, include= T, message= FALSE, warning = FALSE, results='hide'}

# packages used 
packages <- c("tidyverse", "viridis", "sandwich", "lmtest", "magick", "kableExtra",
              "stargazer", "fixest", "rio", "fastDummies", "KRLS", "sensemakr", "here")

# if not installed, install package
installed_libs <- packages %in% rownames(installed.packages())
if(any(installed_libs == FALSE)) {
  install.packages(packages[!installed_libs])
}

# load liberaries
invisible(lapply(packages, library, character.only = T))
```

# Specify root directory

```{r root dir, results='hide', message = FALSE}
# Specify root directory
here::i_am("Replication_Material_Facial_Finetuning.Rmd")
```


# Create directories for tables and figures

```{r tables_figs_dir, results='hide', message = FALSE}
# create directory for tables
if(!dir.exists(here("Tables"))) {
  dir.create(here("Tables"))
}

# create directory for figures
if(!dir.exists(here("Figures"))) {
  dir.create(here("Figures"))
}
```



# RAM and Session info

The code has been run using a Mac with 16 GB RAM with the following packages:

```{r session_info}
sessionInfo()
```



# Figure 1: Out-of-sample evaluation of our fine-tuned model.

```{r fig_1, message=FALSE, warning = FALSE, fig.width=18, fig.height=6}
# load data
load(here("Data", "Annotated.and.predicted.test.data.RData"))

# attractiveness
p1 <- 
  df %>% 
  ggplot(aes(x = annotated_attractiveness, y = predicted_attractiveness)) +
  geom_point(size = 1.5, color = "black", alpha = 0.3) + 
  stat_smooth(method = "loess", se = FALSE, linetype = "longdash", color = "grey40", size = 1) +
  geom_smooth(method = "lm", se = FALSE, color = "black", size = 0.8) +
  xlim(2, 10) +
  ylim(2, 10) +
  theme_bw() +
  xlab(paste("Annotated", "attractivenss")) +
  ylab(paste("Predicted", "attractivenss")) + 
  ggtitle("")


# trustworthiness
p2 <- 
  df %>% 
  ggplot(aes(x = annotated_trustworthiness, y = predicted_trustworthiness)) +
  geom_point(size = 1.5, color = "black", alpha = 0.3) + 
  stat_smooth(method = "loess", se = FALSE, linetype = "longdash", color = "grey40", size = 1) +
  geom_smooth(method = "lm", se = FALSE, color = "black", size = 0.8) +
  xlim(2, 10) +
  ylim(2, 10) +
  theme_bw() +
  xlab(paste("Annotated", "trustworthiness")) +
  ylab(paste("Predicted", "trustworthiness")) + 
  ggtitle("")


# dominance
p3 <- 
  df %>% 
  ggplot(aes(x = annotated_dominance, y = predicted_dominance)) +
  geom_point(size = 1.5, color = "black", alpha = 0.3) + 
  stat_smooth(method = "loess", se = FALSE, linetype = "longdash", color = "grey40", size = 1) +
  geom_smooth(method = "lm", se = FALSE, color = "black", size = 0.8) +
  xlim(2, 10) +
  ylim(2, 10) +
  theme_bw() +
  xlab(paste("Annotated", "dominance")) +
  ylab(paste("Predicted", "dominance")) + 
  ggtitle("")

plots <- gridExtra::grid.arrange(p1, p2, p3, ncol = 3, nrow = 1)

ggsave(plots, filename = here("Figures", "Fig_1.pdf"), height = 6, width = 12)
```



# Figure 2: Main Effects

```{r fig_2, message=FALSE, warning = FALSE, fig.width=8, fig.height=6}
### Load data and transform data________________________________________________

# load data
load(here("Data", "Candidate_data.RData"))

# remove observations without imagges, bad quality images, and images with text
df <- df %>% filter(image_missing == 0, image_bad == 0, image_text == 0)

# recode general election dependent variables
fv22 <- df %>% filter(election == "FV22") %>% 
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes)))

# recode local election dependent variables
kv21 <- df %>% filter(election == "KV21") %>%
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes))) 

# recombine data
df <- rbind(fv22, kv21)

# create second data set by removing observation with only one candidate on the ballot
df2 <- df %>% filter(no_candidates_on_ballot != 1)

### Specify models______________________________________________________________

# define independent variable names
trait_1 <- "attractiveness_pd"
trait_2 <- "trustworthiness_pd"
trait_3 <- "dominance_pd"


# specify models with ballot paper placement as DV
attr_model_bpp_1 <- lm(formula = paste0("percentile_rank_std ~ ", trait_1), data = df2)
attr_model_bpp_2 <- lm(formula = paste0("percentile_rank_std ~ ", trait_1, "+ age + gender + education"), data = df2)

trust_model_bpp_1 <- lm(formula = paste0("percentile_rank_std ~ ", trait_2), data = df2)
trust_model_bpp_2 <- lm(formula = paste0("percentile_rank_std ~ ", trait_2, "+ age + gender + education"), data = df2)

dom_model_bpp_1 <- lm(formula = paste0("percentile_rank_std ~ ", trait_3), data = df2)
dom_model_bpp_2 <- lm(formula = paste0("percentile_rank_std ~ ", trait_3, "+ age + gender + education"), data = df2)

# specify models with personal votes as DV
attr_model_pv_1 <- lm(formula = paste0("personal_votes_std ~ ", trait_1), data = df)
attr_model_pv_2 <- lm(formula = paste0("personal_votes_std ~ ", trait_1, "+ age + gender + education"), data = df)

trust_model_pv_1 <- lm(formula = paste0("personal_votes_std ~ ", trait_2), data = df)
trust_model_pv_2 <- lm(formula = paste0("personal_votes_std ~ ", trait_2, "+ age + gender + education"), data = df)

dom_model_pv_1 <- lm(formula = paste0("personal_votes_std ~ ", trait_3), data = df)
dom_model_pv_2 <- lm(formula = paste0("personal_votes_std ~ ", trait_3, "+ age + gender + education"), data = df)


### Make plot___________________________________________________________________

coefs <- c(
           coef(attr_model_bpp_1)[trait_1], # coefs for attractiveness
           coef(attr_model_bpp_2)[trait_1],
           coef(attr_model_pv_1)[trait_1],
           coef(attr_model_pv_2)[trait_1],
           coef(trust_model_bpp_1)[trait_2], # coefs for trustworthiness
           coef(trust_model_bpp_2)[trait_2],
           coef(trust_model_pv_1)[trait_2],
           coef(trust_model_pv_2)[trait_2],
           coef(dom_model_bpp_1)[trait_3], # coefs for dominance
           coef(dom_model_bpp_2)[trait_3],
           coef(dom_model_pv_1)[trait_3],
           coef(dom_model_pv_2)[trait_3])


# define clustered standard error function
get_standard_error <- function(model, df_name) {
  result <- data.frame(coeftest(model, vcovCL = df2$name2)[, "Std. Error"])
  se <- result[2,1]
  return(se)
}

# get clustered standard errors
se_coefs <- c(
  get_standard_error(attr_model_bpp_1, df2$name2),
  get_standard_error(attr_model_bpp_2, df2$name2),
  get_standard_error(attr_model_pv_1, df$name2),
  get_standard_error(attr_model_pv_2, df$name2),
  get_standard_error(trust_model_bpp_1, df2$name2),
  get_standard_error(trust_model_bpp_2, df2$name2),
  get_standard_error(trust_model_pv_1, df$name2),
  get_standard_error(trust_model_pv_2, df$name2),
  get_standard_error(dom_model_bpp_1, df2$name2),
  get_standard_error(dom_model_bpp_2, df2$name2),
  get_standard_error(dom_model_pv_1, df$name2),
  get_standard_error(dom_model_pv_2, df$name2)
)

# get names for independent and dependent variables
IVs <- c(rep("Attractiveness", 4), rep("Trustworthiness", 4), rep("Dominance", 4))
DVs <- rep(c(rep("Ballot Paper Placement", 2), rep("Personal Votes", 2)), 3)
control <- rep(c("Without control", "With control"), 6)

# combine to data frame
plotdf <- data.frame(coefs, se_coefs, IVs, DVs, control)

# calculate lower and upper intervals
plotdf$li <- plotdf$coefs - 1.96*plotdf$se_coefs
plotdf$ui <- plotdf$coefs + 1.96*plotdf$se_coefs

# adjust the order of the levels for plotting
plotdf$DVs <- factor(plotdf$DVs, levels = rev(c("Ballot Paper Placement","Personal Votes"))) 
plotdf$IVs <- factor(plotdf$IVs, levels = rev(c("Attractiveness", "Trustworthiness", "Dominance")))

# plot
main_effects_plot <- 
  ggplot(plotdf, aes(x = coefs, y = IVs, color = control)) +
  geom_point(aes(shape = DVs), position = position_dodge(width = 0.3)) +
  geom_errorbarh(aes(xmin = li, xmax = ui, shape = DVs), 
                 height = 0.01,
                 position = position_dodge(width = 0.3)) +
  xlim(-0.3, 0.3) +
  scale_x_continuous(breaks = seq(-0.4, 0.4, by = 0.05)) +  
  coord_cartesian() +
  theme_bw() +
  geom_vline(xintercept = 0, linetype = "dashed", color = "gray50", size = 0.5) +
  xlab("Effect") + ylab("") +
  scale_color_manual(values = c("Without control" = "gray50", "With control" = "gray80")) +
  scale_shape_manual(values = c("Personal Votes" = 21, "Ballot Paper Placement" = 16)) +
  scale_shape(solid = TRUE) +
  labs(color = "Control", shape = "Dependent variable") +  
  theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank()) +
  ggtitle("") +
  guides(color = guide_legend(reverse = TRUE, order = 2), shape = guide_legend(reverse = TRUE, order = 1))

ggsave(main_effects_plot, filename = here("Figures", "Fig_2.pdf"), height = 6, width =8)

main_effects_plot
```


# Figure 3: Interaction Effects

```{r fig_3, message=FALSE, warning = FALSE, fig.width=8, fig.height=6}
### Load data and transform data________________________________________________

# load data
load(here("Data", "Candidate_data.RData"))

# remove observations without imagges, bad quality images, and images with text
df <- df %>% filter(image_missing == 0, image_bad == 0, image_text == 0)

# recode general election dependent variables
fv22 <- df %>% filter(election == "FV22") %>% 
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes)))

# recode local election dependent variables
kv21 <- df %>% filter(election == "KV21") %>%
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes))) 

# recombine data
df <- rbind(fv22, kv21)

# create second data set by removing observation with only one candidate on the ballot
df2 <- df %>% filter(no_candidates_on_ballot != 1)



### Specify models______________________________________________________________

# define independent variable names
trait_1 <- "attractiveness_pd"
trait_2 <- "trustworthiness_pd"
trait_3 <- "dominance_pd"

# specify models with ballot paper placement as DV
attr_model_bpp_1 <- lm(formula = paste0("percentile_rank_std ~ ", trait_1, "*gender"), data = df2)
attr_model_bpp_2 <- lm(formula = paste0("percentile_rank_std ~ ", trait_1, "*gender + age + education"), data = df2)
attr_model_bpp_3 <- lm(formula = paste0("percentile_rank_std ~ ", trait_1, "*election_type"), data = df2)
attr_model_bpp_4 <- lm(formula = paste0("percentile_rank_std ~ ", trait_1, "*election_type + age + gender + education"), data = df2)

trust_model_bpp_1 <- lm(formula = paste0("percentile_rank_std ~ ", trait_2, "*gender"), data = df2)
trust_model_bpp_2 <- lm(formula = paste0("percentile_rank_std ~ ", trait_2, "*gender + age + education"), data = df2)
trust_model_bpp_3 <- lm(formula = paste0("percentile_rank_std ~ ", trait_2, "*election_type"), data = df2)
trust_model_bpp_4 <- lm(formula = paste0("percentile_rank_std ~ ", trait_2, "*election_type + age + gender + education"), data = df2)

dom_model_bpp_1 <- lm(formula = paste0("percentile_rank_std ~ ", trait_3, "*party_ideology"), data = df2)
dom_model_bpp_2 <- lm(formula = paste0("percentile_rank_std ~ ", trait_3, "*party_ideology + age + gender  + education"), data = df2)
dom_model_bpp_3 <- lm(formula = paste0("percentile_rank_std ~ ", trait_3, "*election_type"), data = df2)
dom_model_bpp_4 <- lm(formula = paste0("percentile_rank_std ~ ", trait_3, "*election_type + age + gender + education"), data = df2)


# specify models with personal votes as DV
attr_model_pv_1 <- lm(formula = paste0("personal_votes_std ~ ", trait_1, "*gender"), data = df)
attr_model_pv_2 <- lm(formula = paste0("personal_votes_std ~ ", trait_1, "*gender + age + education"), data = df)
attr_model_pv_3 <- lm(formula = paste0("personal_votes_std ~ ", trait_1, "*election_type"), data = df)
attr_model_pv_4 <- lm(formula = paste0("personal_votes_std ~ ", trait_1, "*election_type + age + gender + education"), data = df)

trust_model_pv_1 <- lm(formula = paste0("personal_votes_std ~ ", trait_2, "*gender"), data = df)
trust_model_pv_2 <- lm(formula = paste0("personal_votes_std ~ ", trait_2, "*gender + age + education"), data = df)
trust_model_pv_3 <- lm(formula = paste0("personal_votes_std ~ ", trait_2, "*election_type"), data = df)
trust_model_pv_4 <- lm(formula = paste0("personal_votes_std ~ ", trait_2, "*election_type + age + gender + education"), data = df)

dom_model_pv_1 <- lm(formula = paste0("personal_votes_std ~ ", trait_3, "*party_ideology"), data = df)
dom_model_pv_2 <- lm(formula = paste0("personal_votes_std ~ ", trait_3, "*party_ideology + age + gender + education"), data = df)
dom_model_pv_3 <- lm(formula = paste0("personal_votes_std ~ ", trait_3, "*election_type"), data = df)
dom_model_pv_4 <- lm(formula = paste0("personal_votes_std ~ ", trait_3, "*election_type + age + gender + education"), data = df)


### Make plot___________________________________________________________________

# extract coefficients
coefs <- c(
  # coefs for attractiveness
  coef(attr_model_bpp_1)["attractiveness_pd:gender1"], 
  coef(attr_model_bpp_2)["attractiveness_pd:gender1"],
  coef(attr_model_bpp_3)["attractiveness_pd:election_typeLocal election"], 
  coef(attr_model_bpp_4)["attractiveness_pd:election_typeLocal election"],
  coef(attr_model_pv_1)["attractiveness_pd:gender1"], 
  coef(attr_model_pv_2)["attractiveness_pd:gender1"],
  coef(attr_model_pv_3)["attractiveness_pd:election_typeLocal election"], 
  coef(attr_model_pv_4)["attractiveness_pd:election_typeLocal election"],
  
  # coefs for trustworthiness
  coef(trust_model_bpp_1)["trustworthiness_pd:gender1"], 
  coef(trust_model_bpp_2)["trustworthiness_pd:gender1"],
  coef(trust_model_bpp_3)["trustworthiness_pd:election_typeLocal election"], 
  coef(trust_model_bpp_4)["trustworthiness_pd:election_typeLocal election"],
  coef(trust_model_pv_1)["trustworthiness_pd:gender1"], 
  coef(trust_model_pv_2)["trustworthiness_pd:gender1"],
  coef(trust_model_pv_3)["trustworthiness_pd:election_typeLocal election"], 
  coef(trust_model_pv_4)["trustworthiness_pd:election_typeLocal election"],
  
  # coefs for dominance
  coef(dom_model_bpp_1)["dominance_pd:party_ideology"], 
  coef(dom_model_bpp_2)["dominance_pd:party_ideology"],
  coef(dom_model_bpp_3)["dominance_pd:election_typeLocal election"], 
  coef(dom_model_bpp_4)["dominance_pd:election_typeLocal election"],
  coef(dom_model_pv_1)["dominance_pd:party_ideology"], 
  coef(dom_model_pv_2)["dominance_pd:party_ideology"],
  coef(dom_model_pv_3)["dominance_pd:election_typeLocal election"], 
  coef(dom_model_pv_4)["dominance_pd:election_typeLocal election"])

# define clustered standard error function
get_standard_error <- function(model, df_name) {
  result <- data.frame(coeftest(model, vcovCL = df2$name2)[, "Std. Error"])
  se <- result[nrow(result),1]
  return(se)
}

# get clustered standard errors
se_coefs <- c(
  get_standard_error(attr_model_bpp_1, df2$name2),
  get_standard_error(attr_model_bpp_2, df2$name2),
  get_standard_error(attr_model_bpp_3, df2$name2),
  get_standard_error(attr_model_bpp_4, df2$name2),
  get_standard_error(attr_model_pv_1, df$name2),
  get_standard_error(attr_model_pv_2, df$name2),
  get_standard_error(attr_model_pv_3, df$name2),
  get_standard_error(attr_model_pv_4, df$name2),
  get_standard_error(trust_model_bpp_1, df2$name2),
  get_standard_error(trust_model_bpp_2, df2$name2),
  get_standard_error(trust_model_bpp_3, df2$name2),
  get_standard_error(trust_model_bpp_4, df2$name2),
  get_standard_error(trust_model_pv_1, df$name2),
  get_standard_error(trust_model_pv_2, df$name2),
  get_standard_error(trust_model_pv_3, df$name2),
  get_standard_error(trust_model_pv_4, df$name2),
  get_standard_error(dom_model_bpp_1, df2$name2),
  get_standard_error(dom_model_bpp_2, df2$name2),
  get_standard_error(dom_model_bpp_3, df2$name2),
  get_standard_error(dom_model_bpp_4, df2$name2),
  get_standard_error(dom_model_pv_1, df$name2),
  get_standard_error(dom_model_pv_2, df$name2),
  get_standard_error(dom_model_pv_3, df$name2),
  get_standard_error(dom_model_pv_4, df$name2)
)

# make variables for IVs, DVs, control and interaction terms
IVs <- c(rep("Attractiveness", 8), rep("Trustworthiness", 8), rep("Dominance", 8))
DVs <- rep(c(rep("Ballot Paper Placement", 4), rep("Personal Votes", 4)), 3)
control <- rep(c("Without control", "With control"), 12)
interaction <- c(rep(c(rep("Female",2), rep("Local Election", 2)), 4),
                 rep(c(rep("Right Wing Party", 2), rep("Local Election", 2)), 2))

# combine to data frame
plotdf <- data.frame(coefs, se_coefs, IVs, DVs, control, interaction)

# calculate lower and upper intervals
plotdf$li <- plotdf$coefs - 1.96*plotdf$se_coefs
plotdf$ui <- plotdf$coefs + 1.96*plotdf$se_coefs

# make interaction names for plotting
plotdf$interaction_terms <- paste(plotdf$IVs, "x", plotdf$interaction)

# Adjust the order of the levels for plotting
plotdf$DVs <- factor(plotdf$DVs, levels = rev(c("Ballot Paper Placement","Personal Votes"))) 

plotdf$interaction_terms <- factor(plotdf$interaction_terms, 
                                   levels = rev(c("Attractiveness x Female",
                                              "Attractiveness x Local Election",
                                              "Trustworthiness x Female",
                                              "Trustworthiness x Local Election",
                                              "Dominance x Right Wing Party",
                                              "Dominance x Local Election")))

# plot
interaction_effects_plot <- 
  ggplot(plotdf, aes(x = coefs, y = interaction_terms, color = control)) +
  geom_point(aes(shape = DVs), position = position_dodge(width = 0.3)) +
  geom_errorbarh(aes(xmin = li, xmax = ui, shape = DVs), 
                 height = 0.01,
                 position = position_dodge(width = 0.3)) +
  xlim(-0.3, 0.3) +
  scale_x_continuous(breaks = seq(-0.4, 0.4, by = 0.05)) +  
  coord_cartesian() +
  theme_bw() +
  geom_vline(xintercept = 0, linetype = "dashed", color = "gray50", size = 0.5) +
  xlab("Marginal Effect") + ylab("") +
  scale_color_manual(values = c("Without control" = "gray50", "With control" = "gray80")) +
  scale_shape_manual(values = c("Personal Votes" = 21, "Ballot Paper Placement" = 16)) +
  scale_shape(solid = TRUE) +
  labs(color = "Control", shape = "Dependent variable") +  
  theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank()) +
  ggtitle("") +
  guides(color = guide_legend(reverse = TRUE, order = 2), shape = guide_legend(reverse = TRUE, order = 1))

ggsave(interaction_effects_plot, filename = here("Figures", "Fig_3.pdf"), height = 6, width =8)

interaction_effects_plot
```


# Table A1: Evaluation metrics: attractiveness


```{r tab_A1}
# set name
trait <- "attractiveness"

# load OMI test data with annotated and predicted scores
load(here("Data", "Annotated.and.predicted.test.data.RData"))

# load the full OMI data
load(here("Data", "OMI_training_data.RData"))

colnames(omi_df)[which(colnames(omi_df)==trait)] <- "score"

train_df <- omi_df %>% filter(image_folder == "train")
test_df <- omi_df %>% filter(image_folder == "test")

rows <- c("Number of Images", "Mean", "Standard deviation", "Mean Absolute Error",
          "Pearson's correlation coefficient")

# train data
train_set <- c(
  nrow(train_df), 
  round(mean(train_df$score), 3), 
  round(sd(train_df$score), 3), 
  "", 
  ""
) %>% as.character()

# test data
test_set <- c(
  nrow(test_df), 
  round(mean(test_df$score),3), 
  round(sd(test_df$score),3), 
  "", 
  ""
) %>% as.character()

# model predictions
model_prediction <- c(
  nrow(df), 
  round(mean(df$predicted_attractiveness), 3), 
  round(sd(df$predicted_attractiveness), 3), 
  round(mean(abs(df$annotated_attractiveness - df$predicted_attractiveness)), 3),
  round(cor(df$annotated_attractiveness, df$predicted_attractiveness), 3)
  ) %>% as.character()

# mean guessing
mean_guessing <- c(
  "", 
  "", 
  "", 
  round(mean(abs(df$annotated_attractiveness - mean(train_df$score))), 3), 
  "0.000"
  ) %>% as.character()


# random guessing
set.seed(123)
random_mae <- numeric(1000)
random_pcc <- numeric(1000)
n <- nrow(df)

for (i in 1:1000) {
  random_guesses <- rnorm(n, mean = mean(train_df$score), sd = sd(train_df$score))  # Generate random guesses
  random_mae[i] <- mean(abs(random_guesses - df$annotated_attractiveness))  # Calculate MAE
  random_pcc[i] <- cor(df$annotated_attractiveness, random_guesses, method = "pearson")  # Calculate PCC
}

random_guessing <- c(
  "", 
  "", 
  "", 
  paste(round(mean(random_mae),3), "±", round(sd(random_mae),3)), 
  paste(round(mean(random_pcc),3), "±",  round(sd(random_pcc),3))
  )


data <- data.frame(rows, train_set, test_set, model_prediction, random_guessing, mean_guessing)

# Set the row names using the 'rows' vector
rownames(data) <- data$rows

# Remove the 'rows' column
data$rows <- NULL

# change columns names
colnames(data) <- c("Train set", "Test set", "Model predictions", "Random guessing", "Mean guessing")

# make note
note <- paste("Random guessing is based on predictions drawn from a normal distribution with the mean and standard deviation of the", trait, "score in the training dataset, 1000 runs. Mean guessing is based on always guessing the mean", trait, "score of the train dataset.")

# create and save table
stargazer(data,
          out = here("Tables", "Table_A1.tex"),
          header = FALSE,
          style = "apsr",
          type = "latex",
          summary = FALSE,
          rownames = TRUE,
          column.labels = c("Train set", "Test set", "Model predictions", "Random guessing", "Mean guessing"),
          notes = paste("Note:", note),  
          notes.append = TRUE)
```


# Table A2: Evaluation metrics: trustworthiness


```{r tab_A2}
# set name
trait <- "trustworthiness"

# load OMI test data with annotated and predicted scores
load(here("Data", "Annotated.and.predicted.test.data.RData"))

# load the full OMI data
load(here("Data", "OMI_training_data.RData"))

colnames(omi_df)[which(colnames(omi_df)==trait)] <- "score"

train_df <- omi_df %>% filter(image_folder == "train")
test_df <- omi_df %>% filter(image_folder == "test")


rows <- c("Number of Images", "Mean", "Standard deviation", "Mean Absolute Error",
          "Pearson's correlation coefficient")

# training data
train_set <- c(
  nrow(train_df), 
  round(mean(train_df$score), 3), 
  round(sd(train_df$score), 3), 
  "", 
  ""
) %>% as.character()

# test data
test_set <- c(
  nrow(test_df), 
  round(mean(test_df$score),3), 
  round(sd(test_df$score),3), 
  "", 
  ""
) %>% as.character()

# model predictions
model_prediction <- c(
  nrow(df), 
  round(mean(df$predicted_trustworthiness), 3), 
  round(sd(df$predicted_trustworthiness), 3), 
  round(mean(abs(df$annotated_trustworthiness - df$predicted_trustworthiness)), 3),
  round(cor(df$annotated_trustworthiness, df$predicted_trustworthiness), 3)
  ) %>% as.character()

# mean guessing
mean_guessing <- c(
  "", 
  "", 
  "", 
  round(mean(abs(df$annotated_trustworthiness - mean(train_df$score))), 3), 
  "0.000"
  ) %>% as.character()


# random guessing
set.seed(123)
random_mae <- numeric(1000)
random_pcc <- numeric(1000)
n <- nrow(df)

for (i in 1:1000) {
  random_guesses <- rnorm(n, mean = mean(train_df$score), sd = sd(train_df$score))  # Generate random guesses
  random_mae[i] <- mean(abs(random_guesses - df$annotated_trustworthiness))  # Calculate MAE
  random_pcc[i] <- cor(df$annotated_trustworthiness, random_guesses, method = "pearson")  # Calculate PCC
}

random_guessing <- c(
  "", 
  "", 
  "", 
  paste(round(mean(random_mae),3), "±", round(sd(random_mae),3)), 
  paste(round(mean(random_pcc),3), "±",  round(sd(random_pcc),3))
  )


data <- data.frame(rows, train_set, test_set, model_prediction, random_guessing, mean_guessing)

# Set the row names using the 'rows' vector
rownames(data) <- data$rows

# Remove the 'rows' column
data$rows <- NULL

# change columns names
colnames(data) <- c("Train set", "Test set", "Model predictions", "Random guessing", "Mean guessing")

# make note
note <- paste("Random guessing is based on predictions drawn from a normal distribution with the mean and standard deviation of the", trait, "score in the training dataset, 1000 runs. Mean guessing is based on always guessing the mean", trait, "score of the train dataset.")


# create and save table
stargazer(data,
          out = here("Tables", "Table_A2.tex"),
          header = FALSE,
          style = "apsr",
          type = "latex",
          summary = FALSE,
          rownames = TRUE,
          column.labels = c("Train set", "Test set", "Model predictions", "Random guessing", "Mean guessing"),
          notes = paste("Note:", note),  
          notes.append = TRUE)
```


# Table A3: Evaluation metrics: dominance


```{r tab_A3}
# set name
trait <- "dominance"

# load OMI test data with annotated and predicted scores
load(here("Data", "Annotated.and.predicted.test.data.RData"))

# load the full OMI data
load(here("Data", "OMI_training_data.RData"))

colnames(omi_df)[which(colnames(omi_df)==trait)] <- "score"

train_df <- omi_df %>% filter(image_folder == "train")
test_df <- omi_df %>% filter(image_folder == "test")

rows <- c("Number of Images", "Mean", "Standard deviation", "Mean Absolute Error",
          "Pearson's correlation coefficient")

# training data
train_set <- c(
  nrow(train_df), 
  round(mean(train_df$score), 3), 
  round(sd(train_df$score), 3), 
  "", 
  ""
) %>% as.character()

# test data
test_set <- c(
  nrow(test_df), 
  round(mean(test_df$score),3), 
  round(sd(test_df$score),3), 
  "", 
  ""
) %>% as.character()

# model predictions
model_prediction <- c(
  nrow(df), 
  round(mean(df$predicted_dominance), 3), 
  round(sd(df$predicted_dominance), 3), 
  round(mean(abs(df$annotated_dominance - df$predicted_dominance)), 3),
  round(cor(df$annotated_dominance, df$predicted_dominance), 3)
  ) %>% as.character()

# mean guessing
mean_guessing <- c(
  "", 
  "", 
  "", 
  round(mean(abs(df$annotated_dominance - mean(train_df$score))), 3), 
  "0.000"
  ) %>% as.character()


# random guessing
set.seed(123)
random_mae <- numeric(1000)
random_pcc <- numeric(1000)
n <- nrow(df)

for (i in 1:1000) {
  random_guesses <- rnorm(n, mean = mean(train_df$score), sd = sd(train_df$score))  # Generate random guesses
  random_mae[i] <- mean(abs(random_guesses - df$annotated_dominance))  # Calculate MAE
  random_pcc[i] <- cor(df$annotated_dominance, random_guesses, method = "pearson")  # Calculate PCC
}

random_guessing <- c(
  "", 
  "", 
  "", 
  paste(round(mean(random_mae),3), "±", round(sd(random_mae),3)), 
  paste(round(mean(random_pcc),3), "±",  round(sd(random_pcc),3))
  )


data <- data.frame(rows, train_set, test_set, model_prediction, random_guessing, mean_guessing)

# Set the row names using the 'rows' vector
rownames(data) <- data$rows

# Remove the 'rows' column
data$rows <- NULL

# change columns names
colnames(data) <- c("Train set", "Test set", "Model predictions", "Random guessing", "Mean guessing")

# make note
note <- paste("Random guessing is based on predictions drawn from a normal distribution with the mean and standard deviation of the", trait, "score in the training dataset, 1000 runs. Mean guessing is based on always guessing the mean", trait, "score of the train dataset.")

# create and save table
stargazer(data,
          out = here("Tables", "Table_A3.tex"),
          header = FALSE,
          style = "apsr",
          type = "latex",
          summary = FALSE,
          rownames = TRUE,
          column.labels = c("Train set", "Test set", "Model predictions", "Random guessing", "Mean guessing"),
          notes = paste("Note:", note),  
          notes.append = TRUE)
```



# Figure A1: Evaluation of our fine-tuned models: attractiveness.

```{r fig_A1, message=FALSE, warning = FALSE, fig.width=12, fig.height=12}
# load OMI test data with annotated and predicted scores
load(here("Data", "Annotated.and.predicted.test.data.RData"))

test_df <- df 

# load the full OMI data
load(here("Data", "OMI_training_data.RData"))

colnames(omi_df)[which(colnames(omi_df)=="attractiveness")] <- "score"

# load candidate data
load(here("Data", "Candidate_data.RData"))

# define facial trait name 
trait <- "Attractiveness"

# Predicted vs. Annotated scatter plot
p1 <- 
  test_df %>% 
  ggplot(aes(x = annotated_attractiveness, y = predicted_attractiveness)) +
  geom_point(size = 1.5, color = "#003d73") + 
  stat_smooth(method = "loess", se = FALSE, linetype = "longdash", color = "grey50", size = 1) +
  geom_smooth(method = "lm", se = FALSE, color = "black", size = 0.8) +
  xlim(2, 10) +
  ylim(2, 10) +
  theme_bw() +
  xlab(paste("Annotated", trait)) +
  ylab(paste("Predicted", trait)) + 
  ggtitle("a) Scatter plot of annotated and predicted score")

# Predicted vs. Annotated density plot
p2 <- 
  test_df %>% 
  ggplot(aes(x = annotated_attractiveness, color = "Annotated score")) +
  geom_density(alpha = 0.5, fill = "#009E73") +
  geom_density(aes(x = predicted_attractiveness, color = "Predicted score"), alpha = 0.5, fill = "#0072B2") +
  scale_color_manual(values = c("Annotated score" = "#009E73", "Predicted score" = "#0072B2")) +
  xlim(0, 10) +
  labs(title = "b) Density plot of annotated and predicted score",
       x = trait,
       y = "Density") + theme_bw() +
  theme(legend.title = element_blank(),
        legend.position=c(0.84,.91))

# gender/age plot with full training data set
p3 <- 
  omi_df %>% 
  mutate(gender = ifelse(gender > 50, "Male", "Female")) %>% 
  ggplot(aes(x=factor(gender), y=score)) +
  geom_boxplot(outlier.shape = NA) +
  geom_jitter(aes(color = age), alpha = 0.5, size = 0.5, width = 0.2) +
  scale_color_gradientn(colors = rev(viridis::viridis(10))) +
  xlab("Gender") + ylab(paste("Annotated", trait)) +
  ggtitle("c) Boxplot with annotated scores for training data") +
  theme_bw() + scale_y_continuous(breaks=c(0, 2,4, 6, 8), limits=c(1, 9)) +
  theme(legend.position = "none")

# gender/age plot with full candidate data
p4 <- 
  df %>% 
  mutate(gender = ifelse(gender == 0, "Male", "Female")) %>% 
  ggplot(aes(x=factor(gender), y=attractiveness_pd)) +
  geom_boxplot(outlier.shape = NA) +
  geom_jitter(aes(color = age), alpha = 0.5, size = 0.5, width = 0.2) +
  scale_color_gradientn(colors = rev(viridis::viridis(10))) +
  xlab("Gender") + ylab(paste("Predicted", trait)) +
  ggtitle("d) Boxplot with predicted scores for candidate data") +
  theme_bw() + scale_y_continuous(breaks=c(0, 2,4, 6, 8), limits=c(1, 9)) +
  theme(legend.position=c(.95,.8),
        legend.title = element_text( size=8), legend.text=element_text(size=5),
        legend.key.width=unit(0.3,"cm")) 

evaluation_plot <- gridExtra::grid.arrange(p1, p2, p3, p4, ncol = 2, nrow = 2)

ggsave(evaluation_plot, filename = here("Figures", "Fig_A1.pdf"), width = 12, height = 12)
```


# Figure A2: Evaluation of our fine-tuned models: trustworthiness.

```{r fig_A2, message=FALSE, warning = FALSE, fig.width=12, fig.height=12}

# load OMI test data with annotated and predicted scores
load(here("Data", "Annotated.and.predicted.test.data.RData"))

test_df <- df 

# load the full OMI data
load(here("Data", "OMI_training_data.RData"))

colnames(omi_df)[which(colnames(omi_df)=="trustworthiness")] <- "score"

# load candidate data
load(here("Data", "Candidate_data.RData"))

# define facial trait name 
trait <- "Trustworthiness"

# predicted vs. Annotated scatterplot
p1 <- 
  test_df %>% 
  ggplot(aes(x = annotated_trustworthiness, y = predicted_trustworthiness)) +
  geom_point(size = 1.5, color = "#003d73") + 
  stat_smooth(method = "loess", se = FALSE, linetype = "longdash", color = "grey50", size = 1) +
  geom_smooth(method = "lm", se = FALSE, color = "black", size = 0.8) +
  xlim(2, 10) +
  ylim(2, 10) +
  theme_bw() +
  xlab(paste("Annotated", trait)) +
  ylab(paste("Predicted", trait)) + 
  ggtitle("a) Scatter plot of annotated and predicted score")

# predicted vs. Annotated density plot
p2 <- 
  test_df %>% 
  ggplot(aes(x = annotated_trustworthiness, color = "Annotated score")) +
  geom_density(alpha = 0.5, fill = "#009E73") +
  geom_density(aes(x = predicted_trustworthiness, color = "Predicted score"), alpha = 0.5, fill = "#0072B2") +
  scale_color_manual(values = c("Annotated score" = "#009E73", "Predicted score" = "#0072B2")) +
  xlim(0, 10) +
  labs(title = "b) Density plot of annotated and predicted score",
       x = trait,
       y = "Density") + theme_bw() +
  theme(legend.title = element_blank(),
        legend.position=c(0.84,.91))

# gender/age plot with full training data set
p3 <- 
  omi_df %>% 
  mutate(gender = ifelse(gender > 50, "Male", "Female")) %>% 
  ggplot(aes(x=factor(gender), y=score)) +
  geom_boxplot(outlier.shape = NA) +
  geom_jitter(aes(color = age), alpha = 0.5, size = 0.5, width = 0.2) +
  scale_color_gradientn(colors = rev(viridis::viridis(10))) +
  xlab("Gender") + ylab(paste("Annotated", trait)) +
  ggtitle("c) Boxplot with annotated scores for training data") +
  theme_bw() + scale_y_continuous(breaks=c(0, 2,4, 6, 8), limits=c(1, 9)) +
  theme(legend.position = "none")

# gender/age plot with full candidate data
p4 <- 
  df %>% 
  mutate(gender = ifelse(gender == 0, "Male", "Female")) %>% 
  ggplot(aes(x=factor(gender), y=trustworthiness_pd)) +
  geom_boxplot(outlier.shape = NA) +
  geom_jitter(aes(color = age), alpha = 0.5, size = 0.5, width = 0.2) +
  scale_color_gradientn(colors = rev(viridis::viridis(10))) +
  xlab("Gender") + ylab(paste("Predicted", trait)) +
  ggtitle("d) Boxplot with predicted scores for candidate data") +
  theme_bw() + scale_y_continuous(breaks=c(0, 2,4, 6, 8), limits=c(1, 9)) +
  theme(legend.position=c(.94,.8),
        legend.title = element_text( size=8), legend.text=element_text(size=5),
        legend.key.width=unit(0.3,"cm")) 

evaluation_plot <- gridExtra::grid.arrange(p1, p2, p3, p4, ncol = 2, nrow = 2)

ggsave(evaluation_plot, filename = here("Figures", "Fig_A2.pdf"), width = 12, height = 12)
```

# Figure A3: Evaluation of our fine-tuned models: dominance


```{r fig_A3, message=FALSE, warning = FALSE, fig.width=12, fig.height=12}

# load OMI test data with annotated and predicted scores
load(here("Data", "Annotated.and.predicted.test.data.RData"))

test_df <- df 

# load the full OMI data
load(here("Data", "OMI_training_data.RData"))

colnames(omi_df)[which(colnames(omi_df)=="dominance")] <- "score"

# load candidate data
load(here("Data", "Candidate_data.RData"))

# define facial trait name 
trait <- "Dominance"


# predicted vs. Annotated scatter plot
p1 <- 
  test_df %>% 
  ggplot(aes(x = annotated_dominance, y = predicted_dominance)) +
  geom_point(size = 1.5, color = "#003d73") + 
  stat_smooth(method = "loess", se = FALSE, linetype = "longdash", color = "grey50", size = 1) +
  geom_smooth(method = "lm", se = FALSE, color = "black", size = 0.8) +
  xlim(2, 10) +
  ylim(2, 10) +
  theme_bw() +
  xlab(paste("Annotated", trait)) +
  ylab(paste("Predicted", trait)) + 
  ggtitle("a) Scatter plot of annotated and predicted score")

# Predicted vs. Annotated density plot
p2 <- 
  test_df %>% 
  ggplot(aes(x = annotated_dominance, color = "Annotated score")) +
  geom_density(alpha = 0.5, fill = "#009E73") +
  geom_density(aes(x = predicted_dominance, color = "Predicted score"), alpha = 0.5, fill = "#0072B2") +
  scale_color_manual(values = c("Annotated score" = "#009E73", "Predicted score" = "#0072B2")) +
  xlim(0, 10) +
  labs(title = "b) Density plot of annotated and predicted score",
       x = trait,
       y = "Density") + theme_bw() +
  theme(legend.title = element_blank(),
        legend.position=c(0.84,.91))

# Gender/age plot with full training data set
p3 <- omi_df %>% 
  mutate(gender = ifelse(gender > 50, "Male", "Female")) %>% 
  ggplot(aes(x=factor(gender), y=score)) +
  geom_boxplot(outlier.shape = NA) +
  geom_jitter(aes(color = age), alpha = 0.5, size = 0.5, width = 0.2) +
  scale_color_gradientn(colors = rev(viridis::viridis(10))) +
  xlab("Gender") + ylab(paste("Annotated", trait)) +
  ggtitle("c) Boxplot with annotated scores for training data") +
  theme_bw() + scale_y_continuous(breaks=c(0, 2,4, 6, 8), limits=c(1, 9)) +
  theme(legend.position = "none")

# gender/age plot with full candidate data
p4 <- 
  df %>% 
  mutate(gender = ifelse(gender == 0, "Male", "Female")) %>% 
  ggplot(aes(x=factor(gender), y=dominance_pd)) +
  geom_boxplot(outlier.shape = NA) +
  geom_jitter(aes(color = age), alpha = 0.5, size = 0.5, width = 0.2) +
  scale_color_gradientn(colors = rev(viridis::viridis(10))) +
  xlab("Gender") + ylab(paste("Predicted", trait)) +
  ggtitle("d) Boxplot with predicted scores for candidate data") +
  theme_bw() + scale_y_continuous(breaks=c(0, 2,4, 6, 8), limits=c(1, 9)) +
  theme(legend.position=c(.94,.8),
        legend.title = element_text( size=8), legend.text=element_text(size=5),
        legend.key.width=unit(0.3,"cm")) 

evaluation_plot <- gridExtra::grid.arrange(p1, p2, p3, p4, ncol = 2, nrow = 2)

ggsave(evaluation_plot, filename = here("Figures", "Fig_A3.pdf"), width = 12, height = 12)
```

# Figure A4: Sample images from the “One Million Impression” dataset: Attractiveness.


```{r fig_A4}
# load training data on test images with annotated and predicted scores
load(here("Data", "Annotated.and.predicted.test.data.RData"))

# sample images
sample <- c("947.jpg", "304.jpg" , # two older looking females
            "224.jpg", "18.jpg", # two younger looking females
            "223.jpg", 
            "160.jpg", "609.jpg", # two older looking males 
            "763.jpg", "333.jpg") # two younger looking males

# filter data to sample
df_img <- df %>% filter(Filename %in% sample)

# define facial trait
trait <- "Attractiveness"

# annotated scores
df_img <- df_img %>% arrange(annotated_attractiveness, decreasing = FALSE)
filenames <- df_img$Filename
df_img$letter <- LETTERS[1:9]

# loop through each image and add the annotated text with the corresponding color
for (i in 1:9) {
  img <- image_read(here("Images", "OMI_Images", df_img$Filename[i])) %>% 
    image_resize("244x244") %>%
    image_annotate(paste0(df_img$letter[i], "\n", 
                          round(df_img$annotated_attractiveness[i], 2)), 
                   size = 50, color = "#F0E442", strokecolor = "black")
  
  assign(paste0("img", i), img)
}

first <- image_append(c(img1, img2, img3), stack = FALSE)
sec <- image_append(c(img4, img5, img6), stack = FALSE)
third <- image_append(c(img7, img8, img9), stack = FALSE)
annotated_plot <- image_append(c(first, sec, third), stack = TRUE)

# add empty border on top of image
top_border <- image_blank(width = image_info(annotated_plot)$width, height = 30, color = "white")
annotated_plot <- image_append(c(top_border, annotated_plot), stack = TRUE)

# Add a title to plot
title_text <- paste("Annotated", trait)
annotated_plot <- image_annotate(annotated_plot, title_text, font = "serif", location = "+0+0", size = 30, color = "black")

# predicted scores
df_img <- df_img %>% arrange(predicted_attractiveness, decreasing = FALSE)

# predicted scores
for (i in 1:9) {
  img <- image_read(here("Images", "OMI_images", df_img$Filename[i])) %>% 
    image_resize("244x244") %>%
    image_annotate(paste0(df_img$letter[i], "\n", round(df_img$predicted_attractiveness[i], 2)), size = 50, color = "#F0E442",
                   strokecolor = "black")
  assign(paste0("img", i), img)
}

# combine images
first <- image_append(c(img1, img2, img3), stack = FALSE)
sec <- image_append(c(img4, img5, img6), stack = FALSE)
third <- image_append(c(img7, img8, img9), stack = FALSE)
predicted_plot <- image_append(c(first, sec, third), stack = TRUE)

# add empty border on top of image
top_border <- image_blank(width = image_info(predicted_plot)$width, height = 30, color = "white")
predicted_plot <- image_append(c(top_border, predicted_plot), stack = TRUE)


# Add a title to plot
title_text <- paste("Predicted", trait)
predicted_plot <- image_annotate(predicted_plot, title_text, font = "serif", location = "+0+0", size = 30, color = "black")


# create empty image for spacing between the annotated and predicted plot
spacing <- image_blank(width = 30, image_info(predicted_plot)$height, color = "white")  

# combine plots
final_plot <-  image_append(c(annotated_plot, spacing, predicted_plot), stack = FALSE)

# save image
image_write(final_plot, path = here("Figures", "Fig_A4.jpeg"), format = "jpeg")

# print image
print(final_plot)
```




# Figure A5: Sample images from the “One Million Impression” dataset: Trustworthiness.

```{r fig_A5}
# load training data on test images with annotated and predicted scores
load(here("Data", "Annotated.and.predicted.test.data.RData"))

# sample images
sample <- c("947.jpg", "304.jpg" , # two older looking females
            "224.jpg", "18.jpg", # two younger looking females
            "223.jpg", 
            "160.jpg", "609.jpg", # two older looking males 
            "763.jpg", "333.jpg") # two younger looking males


trait <- "Trustworthiness"

# annotated scores
df_img <- df_img %>% arrange(annotated_trustworthiness, decreasing = FALSE)
filenames <- df_img$Filename
df_img$letter <- LETTERS[1:9]

# loop through each image and add the annotated text with the corresponding color
for (i in 1:9) {
  img <- image_read(here("Images", "OMI_Images", df_img$Filename[i])) %>% 
    image_resize("244x244") %>%
    image_annotate(paste0(df_img$letter[i], "\n", round(df_img$annotated_trustworthiness[i], 2)), size = 50, color = "#F0E442",
                   strokecolor = "black")
  assign(paste0("img", i), img)
}

first <- image_append(c(img1, img2, img3), stack = FALSE)
sec <- image_append(c(img4, img5, img6), stack = FALSE)
third <- image_append(c(img7, img8, img9), stack = FALSE)
annotated_plot <- image_append(c(first, sec, third), stack = TRUE)

# add empty border on top of image
top_border <- image_blank(width = image_info(annotated_plot)$width, height = 30, color = "white")
annotated_plot <- image_append(c(top_border, annotated_plot), stack = TRUE)

# add a title to plot
title_text <- paste("Annotated", trait)
annotated_plot <- image_annotate(annotated_plot, title_text, font = "serif", location = "+0+0", size = 30, color = "black")

# predicted scores
df_img <- df_img %>% arrange(predicted_trustworthiness, decreasing = FALSE)

# predicted scores
for (i in 1:9) {
  img <- image_read(here("Images", "OMI_Images", df_img$Filename[i])) %>% 
    image_resize("244x244") %>%
    image_annotate(paste0(df_img$letter[i], "\n", round(df_img$predicted_trustworthiness[i], 2)), size = 50, color = "#F0E442",
                   strokecolor = "black")
  assign(paste0("img", i), img)
}

# combine images
first <- image_append(c(img1, img2, img3), stack = FALSE)
sec <- image_append(c(img4, img5, img6), stack = FALSE)
third <- image_append(c(img7, img8, img9), stack = FALSE)
predicted_plot <- image_append(c(first, sec, third), stack = TRUE)

# add empty border on top of image
top_border <- image_blank(width = image_info(predicted_plot)$width, height = 30, color = "white")
predicted_plot <- image_append(c(top_border, predicted_plot), stack = TRUE)


# add a title to plot
title_text <- paste("Predicted", trait)
predicted_plot <- image_annotate(predicted_plot, title_text, font = "serif", location = "+0+0", size = 30, color = "black")


# create empty image for spacing between the annotated and predicted plot
spacing <- image_blank(width = 30, image_info(predicted_plot)$height, color = "white")  

# combine plots
final_plot <-  image_append(c(annotated_plot, spacing, predicted_plot), stack = FALSE)

# save image
image_write(final_plot, path = here("Figures", "Fig_A5.jpeg"), format = "jpeg")

# print image
print(final_plot)
```


# Figure A6: Sample images from the “One Million Impression” dataset: Dominance.

```{r fig_A6}
# load training data on test images with annotated and predicted scores
load(here("Data", "Annotated.and.predicted.test.data.RData"))

# sample images
sample <- c("947.jpg", "304.jpg" , # two older looking females
            "224.jpg", "18.jpg", # two younger looking females
            "223.jpg", 
            "160.jpg", "609.jpg", # two older looking males 
            "763.jpg", "333.jpg") # two younger looking males

# filter data to sample
df_img <- df %>% filter(Filename %in% sample)


trait <- "Dominance"

# annotated scores
df_img <- df_img %>% arrange(annotated_dominance, decreasing = FALSE)
filenames <- df_img$Filename
df_img$letter <- LETTERS[1:9]

# loop through each image and add the annotated text with the corresponding color
for (i in 1:9) {
  img <- image_read(here("Images", "OMI_Images", df_img$Filename[i])) %>% 
    image_resize("244x244") %>%
    image_annotate(paste0(df_img$letter[i], "\n", round(df_img$annotated_dominance[i], 2)), size = 50, color = "#F0E442",
                   strokecolor = "black")
  assign(paste0("img", i), img)
}

first <- image_append(c(img1, img2, img3), stack = FALSE)
sec <- image_append(c(img4, img5, img6), stack = FALSE)
third <- image_append(c(img7, img8, img9), stack = FALSE)
annotated_plot <- image_append(c(first, sec, third), stack = TRUE)

# add empty border on top of image
top_border <- image_blank(width = image_info(annotated_plot)$width, height = 30, color = "white")
annotated_plot <- image_append(c(top_border, annotated_plot), stack = TRUE)

# add a title to plot
title_text <- paste("Annotated", trait)
annotated_plot <- image_annotate(annotated_plot, title_text, font = "serif", location = "+0+0", size = 30, color = "black")

# predicted scores
df_img <- df_img %>% arrange(predicted_dominance, decreasing = FALSE)

# predicted scores
for (i in 1:9) {
  img <- image_read(here("Images", "OMI_Images", df_img$Filename[i])) %>% 
    image_resize("244x244") %>%
    image_annotate(paste0(df_img$letter[i], "\n", round(df_img$predicted_dominance[i], 2)), size = 50, color = "#F0E442",
                   strokecolor = "black")
  assign(paste0("img", i), img)
}

# combine images
first <- image_append(c(img1, img2, img3), stack = FALSE)
sec <- image_append(c(img4, img5, img6), stack = FALSE)
third <- image_append(c(img7, img8, img9), stack = FALSE)
predicted_plot <- image_append(c(first, sec, third), stack = TRUE)

# add empty border on top of image
top_border <- image_blank(width = image_info(predicted_plot)$width, height = 30, color = "white")
predicted_plot <- image_append(c(top_border, predicted_plot), stack = TRUE)


# add a title to plot
title_text <- paste("Predicted", trait)
predicted_plot <- image_annotate(predicted_plot, title_text, font = "serif", location = "+0+0", size = 30, color = "black")


# create empty image for spacing between the annotated and predicted plot
spacing <- image_blank(width = 30, image_info(predicted_plot)$height, color = "white")  

# combine plots
final_plot <-  image_append(c(annotated_plot, spacing, predicted_plot), stack = FALSE)

# save image
image_write(final_plot, path = here("Figures", "Fig_A6.jpeg"), format = "jpeg")

# print image
print(final_plot)
```


# Figure A7: Sample images from the Chicago Face Database.

```{r fig_A7}
# get list of images
imgs <- list.files(here("Images", "CFD_Images"))

# Create a list to store images
image_list <- list()

for (i in seq_along(imgs)) {
  img <- image_read(here("Images", "CFD_Images", imgs[i])) %>% 
    image_resize("244x244")
  
  # Assign the image to the list
  image_list[[paste0("img", i)]] <- img
}

# combine plots
cfd <- image_append(c(image_list[[1]], image_list[[2]], image_list[[3]], image_list[[4]]))

# save image
image_write(cfd, path = here("Figures", "Fig_A7.jpeg"), format = "jpeg")

# print image
print(cfd)
```


# Table A4: Overview of collected and missing data.

```{r tab_A4}
# load candidate data
load(here("Data", "Candidate_data.RData"))

# create data frame 
Election = c("General", "Local", "Pooled", "Pooled (%)", "Personal votes accounted for by observations (%)")

data <- data.frame(
  Election,
  Major_party_candidates = rep(NA, length(Election)),
  Images_collected = rep(NA, length(Election)),
  Images_missing = rep(NA, length(Election)),
  Images_discarded = rep(NA, length(Election)),
  Age = rep(NA, length(Election)),
  Gender = rep(NA, length(Election)),
  Education = rep(NA, length(Election)),
  Complete_data = rep(NA, length(Election))
)


# general election
data[1,2] <- df %>% filter(election == "FV22") %>% nrow() # major party candidates
data[1,3] <- df %>% filter(election == "FV22", image_missing == 0) %>% nrow() # non-missing images 
data[1,4] <- df %>% filter(election == "FV22", image_missing == 1) %>% nrow() # missing images
data[1,5] <- df %>% filter(election == "FV22", image_text == 1) %>% nrow() + df %>% filter(election == "FV22", image_bad == 1) %>% nrow() # images discarded
data[1,6] <- df %>% filter(election == "FV22", !is.na(age)) %>% nrow() # candidates with age information
data[1,7] <- df %>% filter(election == "FV22", !is.na(gender)) %>% nrow() # candidates with gender information
data[1,8] <- df %>% filter(election == "FV22", !is.na(education)) %>% nrow() # candidates with education information
data[1,9] <- df %>% filter(election == "FV22", image_missing == 0, image_bad == 0, image_text == 0) %>%  # complete data
  dplyr::select(personal_votes, ballot_placement, age, gender, education) %>% filter(complete.cases(.)) %>% nrow() 
# local election
data[2,2] <- df %>% filter(election == "KV21") %>% nrow()
data[2,3] <- df %>% filter(election == "KV21", image_missing == 0) %>% nrow()
data[2,4] <- df %>% filter(election == "KV21", image_missing == 1) %>% nrow()
data[2,5] <- df %>% filter(election == "KV21", image_missing == 0, image_text == 1) %>% nrow() + df %>% filter(election == "KV21", image_missing == 0, image_bad == 1) %>% nrow()
data[2,6] <- df %>% filter(election == "KV21", !is.na(age)) %>% nrow()
data[2,7] <- df %>% filter(election == "KV21", !is.na(gender)) %>% nrow()
data[2,8] <- df %>% filter(election == "KV21", !is.na(education)) %>% nrow()
data[2,9] <- df %>% filter(election == "KV21", image_missing == 0, image_bad == 0, image_text == 0) %>% 
  dplyr::select(personal_votes, ballot_placement, age, gender, education) %>% filter(complete.cases(.)) %>% nrow() 

# Pooled election data
data[3,2] <- df %>% nrow()
data[3,3] <- df %>% filter(image_missing == 0) %>% nrow()
data[3,4] <- df %>% filter(image_missing == 1) %>% nrow()
data[3,5] <- df %>% filter(image_missing == 0, image_text == 1) %>% nrow() + df %>% filter(image_missing == 0, image_bad == 1) %>% nrow()
data[3,6] <- df %>% filter(!is.na(age)) %>% nrow()
data[3,7] <- df %>% filter(!is.na(gender)) %>% nrow()
data[3,8] <- df %>% filter(!is.na(education)) %>% nrow()
data[3,9] <- df %>% filter(image_missing == 0, image_bad == 0, image_text == 0) %>% 
  dplyr::select(personal_votes, ballot_placement, age, gender, education) %>% filter(complete.cases(.)) %>% nrow() 

# pooled data (%)
data[,2] <- as.numeric(data[,2])
data[,3] <- as.numeric(data[,3])
data[,4] <- as.numeric(data[,4])
data[,5] <- as.numeric(data[,5])
data[,6] <- as.numeric(data[,6])
data[,7] <- as.numeric(data[,7])
data[,8] <- as.numeric(data[,8])
data[,9] <- as.numeric(data[,9])


data[4,2] <- round(data[3,2]/data[3,2]*100,2)
data[4,3] <- round(data[3,3]/data[3,2]*100,2)
data[4,4] <- round(data[3,4]/data[3,2]*100,2)
data[4,5] <- round(data[3,5]/data[3,2]*100,2)
data[4,6] <- round(data[3,6]/data[3,2]*100,2)
data[4,7] <- round(data[3,7]/data[3,2]*100,2)
data[4,8] <- round(data[3,8]/data[3,2]*100,2)
data[4,9] <- round(data[3,9]/data[3,2]*100,2)


# calculate percentage of personal votes accounted for by observations

df$images_disc <- ifelse(df$image_text==1|df$image_bad==1,1,0)

# Pooled
data[5,2] <- 100

dat <- df %>% filter(image_missing == 0) 
data[5,3] <- round(sum(dat$personal_votes)/sum(df$personal_votes) * 100, 2)

# image missing 
dat <- df %>% filter(image_missing == 1)
data[5,4] <- round(sum(dat$personal_votes)/sum(df$personal_votes) * 100, 2)

# image text
dat1 <- df %>% filter(image_text == 1)
dat2 <- df %>% filter(image_bad == 1)
data[5,5] <- round((sum(dat1$personal_votes) + sum(dat2$personal_votes))/sum(df$personal_votes) * 100, 2)

# age
dat <- df %>% filter(!is.na(age))
data[5,6] <- round(sum(dat$personal_votes)/sum(df$personal_votes) * 100, 2)

# gender
dat <- df %>% filter(!is.na(gender))
data[5,7] <- round(sum(dat$personal_votes)/sum(df$personal_votes) * 100, 2)

# education
dat <- df %>% filter(!is.na(education))
data[5,8] <- round(sum(dat$personal_votes)/sum(df$personal_votes) * 100, 2)

# images
dat <- df %>% filter(image_missing == 0, image_bad == 0, image_text == 0) %>% 
  dplyr::select(personal_votes, ballot_placement, age, gender, education) %>% filter(complete.cases(.))
data[5,9] <- round(sum(dat$personal_votes)/sum(df$personal_votes) * 100, 2)

# convert to character
data[] <- lapply(data, as.character)

table <- data %>%
  kbl(format = "latex", booktabs = TRUE, align = "lcccccccc",
      col.names = c("Election", "Major party candidates", "Images collected", "Images missing", "Images discarded", "Age", "Gender", "Education", "Complete data")) %>%
  kable_classic() %>%
  footnote(general = "The first three rows indicate the number of observations in absolute numbers. Fourth row indicate the number of observations in percent in relation to the pooled number of major party candidates. The fifth row indicates the percentage of personal votes accounted for by the observations in each column in relation to the personal votes accounted for by the major party candidates")

# save table
writeLines(table, here("Tables", "Table_A4.tex"))

table %>% cat()
```



# Table A5: Descriptive statistics on outcome variables for missing candidates across parties

```{r tab_A5}
# load data
load(here("Data", "Candidate_data.RData"))

df <- df %>% 
  filter(image_missing == 1) %>%
  select(party, ballot_placement, personal_votes)


# create data frame 
parties <- table(df$party)

data <- data.frame(
  party = parties,
  mean_bp = rep("", length(parties)),
  sd_bp = rep("", length(parties)), 
  mean_pv = rep("", length(parties)),
  sd_pv = rep("", length(parties))
)

# add data to data frame
for(i in 1:nrow(data)){
  data$mean_bp[i] <- round(mean(df$ballot_placement[df$party == data$party.Var1[i]]), 2)
  data$sd_bp[i] <- round(sd(df$ballot_placement[df$party == data$party.Var1[i]]), 2)
  data$mean_pv[i] <- round(mean(df$personal_votes[df$party == data$party.Var1[i]]), 2)
  data$sd_pv[i] <- round(sd(df$personal_votes[df$party == data$party.Var1[i]]), 2)
}

table <- data %>%
  kbl(format = "latex", booktabs = TRUE, align = "lccccc",
      col.names = c("Party", "N", "Mean Ballot Placement", "St. Dev. Ballot Placement", "Mean Personal Votes", "St. Dev. Personal Votes")) %>%
  kable_classic() %>% gsub("\\\\addlinespace", "", .)

# save table
writeLines(table, here("Tables", "Table_A5.tex"))

table %>% cat()
```


# Table A6: Summary statistics for covariates for candidates with a photo

```{r tab_A6}
# load candidate data
load(here("Data", "Candidate_data.RData"))


df <- df %>% 
  filter(image_missing == 0, image_bad == 0, image_text == 0) %>%
  select(age, gender, ethnicity)


# create data frame 
variables <- c("Age", "Gender", "Male", "Female", "Ethnicity", "African", "Asian", "Middle-Eastern", "White")

data <- data.frame(
  Variable = variables,
  N = rep("", length(variables)),
  Mean = rep("", length(variables)),
  Min = rep("", length(variables)),
  SD = rep("", length(variables)),
  p25 = rep("", length(variables)),
  Median = rep("", length(variables)),
  p75 = rep("", length(variables)),
  max = rep("", length(variables))
)

# add data to data frame
data[1, 2] <- nrow(df[!is.na(df$age),])
data[1, 3] <- round(mean(df$age[!is.na(df$age)]),1)
data[1, 4] <- min(df$age[!is.na(df$age)])
data[1, 5] <- round(sd(df$age[!is.na(df$age)]),1)
data[1, 6] <- quantile(df$age[!is.na(df$age)], 0.25, names = FALSE)
data[1, 7] <- quantile(df$age[!is.na(df$age)], 0.5, names = FALSE)
data[1, 8] <- quantile(df$age[!is.na(df$age)], 0.75, names = FALSE)
data[1, 9] <- max(df$age[!is.na(df$age)])
data[3, 2] <- paste0(nrow(df[!is.na(df$gender) & df$gender == 0,]), " (", round(nrow(df[!is.na(df$gender) & df$gender == 0,])/nrow(df[!is.na(df$gender),])*100, 1), "%)") 
data[4, 2] <- paste0(nrow(df[!is.na(df$gender) & df$gender == 1,]), " (", round(nrow(df[!is.na(df$gender) & df$gender == 1,])/nrow(df[!is.na(df$gender),])*100, 1), "%)") 
data[6, 2] <- paste0(nrow(df[!is.na(df$ethnicity) & df$ethnicity == "African",]), " (", round(nrow(df[!is.na(df$ethnicity) & df$ethnicity == "African",])/nrow(df[!is.na(df$ethnicity),])*100, 1), "%)") 
data[7, 2] <- paste0(nrow(df[!is.na(df$ethnicity) & df$ethnicity == "Asian",]), " (", round(nrow(df[!is.na(df$ethnicity) & df$ethnicity == "Asian",])/nrow(df[!is.na(df$ethnicity),])*100, 1), "%)") 
data[8, 2] <- paste0(nrow(df[!is.na(df$ethnicity) & df$ethnicity == "Middle-Eastern",]), " (", round(nrow(df[!is.na(df$ethnicity) & df$ethnicity == "Middle-Eastern",])/nrow(df[!is.na(df$ethnicity),])*100, 1), "%)")
data[9, 2] <- paste0(nrow(df[!is.na(df$ethnicity) & df$ethnicity == "White",]), " (", round(nrow(df[!is.na(df$ethnicity) & df$ethnicity == "White",])/nrow(df[!is.na(df$ethnicity),])*100, 1), "%)")

table <- data %>%
  kbl(format = "latex", booktabs = TRUE, align = "lcccccccc",
      col.names = c("", "N", "Mean", "St. Dev.", "Min", "Q1","Median", "Q3", "Max")) %>%
  kable_classic() %>% gsub("\\\\addlinespace", "", .)


# save table
writeLines(table, here("Tables", "Table_A6.tex"))

table %>% cat()
```


# Table A7: Summary statistics for predicted facial traits

```{r tab_A7_1, results='hide'}
# load candidate data
load(here("Data", "Candidate_data.RData"))

# remove observations without imagges, bad quality images, and images with text
df <- df %>% filter(image_missing == 0, image_bad == 0, image_text == 0) %>%
  select(attractiveness_pd, trustworthiness_pd, dominance_pd)

# create summary table
table <- stargazer::stargazer(df,
                     header=FALSE,
                     summary=TRUE,
                     type = "latex", 
                     digits=1,
                     style = "apsr",
                     summary.stat = c("n", "mean", "sd", "min", "p25", "median", "p75", "max"),
                     covariate.labels = c("Attractiveness", "Trustworthiness", "Dominance")
                     )
```

```{r tab_A7_2}
# change column titles
table <- table %>% gsub("Pctl\\(25\\)", "Q1", .) %>% gsub("Pctl\\(75\\)", "Q3", .) %>% paste(collapse = "\n")

# save table
writeLines(table, here("Tables", "Table_A7.tex"))

# print
table %>% cat()
```

# Table A8: Correlations between predicted facial traits


```{r tab_A8}
# load candidate data
load(here("Data", "Candidate_data.RData"))

# clean data
traits <- df %>% 
  filter(image_missing == 0, image_bad == 0, image_text == 0) %>% 
  dplyr::select(attractiveness_pd, trustworthiness_pd, dominance_pd) 

# make data frame with correlations
data <- data.frame(round(cor(traits), 3))

# change columns and row names
colnames(data) <- c("Attractiveness", "Trustworthiness", "Dominance")
rownames(data) <- c("Attractiveness", "Trustworthiness", "Dominance")

# create and save table
stargazer(data,
          out = here("Tables", "Table_A8.tex"),
          header = FALSE,
          style = "apsr",
          type = "latex",
          summary = FALSE,
          rownames = TRUE)
```


# Table A9: Facial traits across parties.

```{r tab_A9}
# load candidate data
load(here("Data", "Candidate_data.RData"))

# create data frame 
Party <- sort(unique(df$party))

data <- data.frame(
  Party = Party,
  Attractiveness = rep(NA, length(Party)),
  Trustworthiness = rep(NA, length(Party)),
  Dominance = rep(NA, length(Party))
)

# add means to data frame
for(i in 1:nrow(data)) {
  
  party <- data$Party[i]
  
  data$Attractiveness[i] <- round(mean(df$attractiveness_pd[df$party == party],na.rm = TRUE),2)
  data$Trustworthiness[i] <- round(mean(df$trustworthiness_pd[df$party == party],na.rm = TRUE),2)
  data$Dominance[i] <- round(mean(df$dominance_pd[df$party == party],na.rm = TRUE),2)
  
}

# create table
table <- data %>%
  kbl(format = "latex", booktabs = TRUE, align = "lccc",
      col.names = c("Party", "Mean Attractiveness","Mean Trustworthiness", "Mean Dominance")) %>%
  kable_classic() %>% gsub("\\\\addlinespace", "", .)

# save table
writeLines(table, here("Tables", "Table_A9.tex"))

# print
table %>% cat()
```



# Table A10: Regression results: attractiveness

```{r tab_A10, warning = FALSE}
### Load data and transform data________________________________________________

# load candidate data
load(here("Data", "Candidate_data.RData"))

# remove observations without imagges, bad quality images, and images with text
df <- df %>% filter(image_missing == 0, image_bad == 0, image_text == 0)

# recode general election dependent variables
fv22 <- df %>% filter(election == "FV22") %>% 
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes)))

# recode local election dependent variables
kv21 <- df %>% filter(election == "KV21") %>%
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes))) 

# recombine data
df <- rbind(fv22, kv21)

# create second data set by removing observation with only one candidate on the ballot
df2 <- df %>% filter(no_candidates_on_ballot != 1)


# define names 
trait <- "Attractiveness"
trait2 <- "attractiveness_pd"
election <- "Pooled Data"


# Define clustered standard error function
se_robust <- function(x) {
  
  coeftest(x, vcovCL = df2$name2)[, "Std. Error"]
}


### Specify models______________________________________________________________

# Ballot percentile rank - bivariate
model1a <- lm(formula = paste0("percentile_rank_std ~ ", trait2), data = df2)

# Ballot percentile rank + controls 
model1b <- lm(formula = paste0("percentile_rank_std ~ ", trait2, "+ age + gender + education"), data = df2)

# Ballot percentile rank + gender interaction
model1c <- lm(formula = paste0("percentile_rank_std ~ ", trait2, "*gender"), data = df2)

# Ballot percentile rank + gender interaction + controls
model1d <- lm(formula = paste0("percentile_rank_std ~ ", trait2, "*gender + age + education"), data = df2)

# Ballot percentile rank + election interaction
model1e <- lm(formula = paste0("percentile_rank_std ~ ", trait2, "*election_type"), data = df2)

# Ballot percentile rank + election interaction + controls
model1f <- lm(formula = paste0("percentile_rank_std ~ ", trait2, "*election_type + age + gender + education"), data = df2)


# Log(personal votes) std - bivariate
model2a <- lm(formula = paste0("personal_votes_std ~ ", trait2), data = df)

# Log(personal votes) std + controls 
model2b <- lm(formula = paste0("personal_votes_std ~ ", trait2, "+ age + gender + education"), data = df)

# Log(personal votes) std + gender interaction
model2c <- lm(formula = paste0("personal_votes_std ~ ", trait2, "*gender"), data = df)

# Log(personal votes) std + gender interaction + controls
model2d <- lm(formula = paste0("personal_votes_std ~ ", trait2, "*gender + age + education"), data = df)

# Log(personal votes) std + election interaction
model2e <- lm(formula = paste0("personal_votes_std ~ ", trait2, "*election_type"), data = df)

# Log(personal votes) std + election interaction + controls
model2f <- lm(formula = paste0("personal_votes_std ~ ", trait2, "*election_type + age + gender + education"), data = df)

# List models
models <- list(model1a, model1b, model1c, model1d, model1e, model1f, 
               model2a, model2b, model2c, model2d, model2e, model2f)


### Create table________________________________________________________________

# create and save table
stargazer(models,
          out = here("Tables", "Table_A10.tex"),
          header = FALSE,
          style = "apsr",
          type = "latex",
          dep.var.labels = c("Ballot Paper Placement", "Personal Votes"),
          order = c("attractiveness_pd", 
                    "attractiveness_pd:gender1",
                    "attractiveness_pd:election_typeLocal election",
                    "election_typeLocal election",
                    "gender", "age", "Education Control"),
          covariate.labels = c(trait, paste(trait, "x Female"),
                               paste(trait, "x Local Election"),
                               "Local Election", "Female", "Age"), 
          se = lapply(models, se_robust),
          no.space = FALSE,
          single.row = FALSE,
          df = FALSE,
          initial.zero = FALSE,
          star.cutoffs = c(0.05, 0.01, 0.001),
          align=FALSE,
          model.numbers = TRUE,
          omit.stat = c("ser"),
          omit = c("education"),
          add.lines = list(c("Education Control", "No", "Yes", "No", "Yes", "No", "Yes", "No", "Yes", "No", "Yes", "No", "Yes")),
          notes = c("Note: Entries are unstandardized coefficients from linear models with clustered standard errors. *p \\textless{} .05; **p \\textless{} .01; ***p \\textless{} .001."),
          notes.append = FALSE)
```


# Table A11: Regression results: trustworthiness

```{r tab_A11, warning = FALSE}
### Load data and transform data________________________________________________

# load candidate data
load(here("Data", "Candidate_data.RData"))

# remove observations without imagges, bad quality images, and images with text
df <- df %>% filter(image_missing == 0, image_bad == 0, image_text == 0)

# recode general election dependent variables
fv22 <- df %>% filter(election == "FV22") %>% 
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes)))

# recode local election dependent variables
kv21 <- df %>% filter(election == "KV21") %>%
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes))) 

# recombine data
df <- rbind(fv22, kv21)

# create second data set by removing observation with only one candidate on the ballot
df2 <- df %>% filter(no_candidates_on_ballot != 1)

# define names
trait <- "Trustworthiness"
trait2 <- "trustworthiness_pd"
election <- "Pooled data"

# Define clustered standard error function
se_robust <- function(x) {
  
  coeftest(x, vcovCL = df2$name2)[, "Std. Error"]
}


### Specify Models______________________________________________________________

# Ballot percentile rank - bivariate
model1a <- lm(formula = paste0("percentile_rank_std ~ ", trait2), data = df2)

# Ballot percentile rank + controls 
model1b <- lm(formula = paste0("percentile_rank_std ~ ", trait2, "+ age + gender + education"), data = df2)

# Ballot percentile rank + gender interaction
model1c <- lm(formula = paste0("percentile_rank_std ~ ", trait2, "*gender"), data = df2)

# Ballot percentile rank + gender interaction + controls
model1d <- lm(formula = paste0("percentile_rank_std ~ ", trait2, "*gender + age + education"), data = df2)

# Ballot percentile rank + election interaction
model1e <- lm(formula = paste0("percentile_rank_std ~ ", trait2, "*election_type"), data = df2)

# Ballot percentile rank + election interaction + controls
model1f <- lm(formula = paste0("percentile_rank_std ~ ", trait2, "*election_type + age + gender + education"), data = df2)

# Log(personal votes) std - bivariate
model2a <- lm(formula = paste0("personal_votes_std ~ ", trait2), data = df)

# Log(personal votes) std + controls 
model2b <- lm(formula = paste0("personal_votes_std ~ ", trait2, "+ age + gender + education"), data = df)

# Log(personal votes) std + gender interaction
model2c <- lm(formula = paste0("personal_votes_std ~ ", trait2, "*gender"), data = df)

# Log(personal votes) std + gender interaction + controls
model2d <- lm(formula = paste0("personal_votes_std ~ ", trait2, "*gender + age + education"), data = df)

# Log(personal votes) std + election interaction
model2e <- lm(formula = paste0("personal_votes_std ~ ", trait2, "*election_type"), data = df)

# Log(personal votes) std + election interaction + controls
model2f <- lm(formula = paste0("personal_votes_std ~ ", trait2, "*election_type + age + gender + education"), data = df)

# List models
models <- list(model1a, model1b, model1c, model1d, model1e, model1f, 
               model2a, model2b, model2c, model2d, model2e, model2f)

### Create table________________________________________________________________

# create and save table
stargazer(models,
          out = here("Tables", "Table_A11.tex"),
          header = FALSE,
          type = "latex",
          style = "apsr",
          dep.var.labels = c("Ballot Paper Placement", "Personal Votes"),
          order = c("trustworthiness_pd", 
                    "trustworthiness_pd:gender1",
                    "trustworthiness_pd:election_typeLocal election",
                    "election_typeLocal election",
                    "gender", "age"),
          covariate.labels = c(trait, paste(trait, "x Female"),
                               paste(trait, "x Local Election"),
                               "Local Election", "Female", "Age"), 
          se = lapply(models, se_robust),
          no.space = FALSE,
          single.row = FALSE,
          df = FALSE,
          initial.zero = FALSE,
          star.cutoffs = c(0.05, 0.01, 0.001),
          align=FALSE,
          model.numbers = TRUE,
          omit.stat = c("ser"),
          omit = c("education"),
          add.lines = list(c("Education Control", "No", "Yes", "No", "Yes", "No", "Yes", "No", "Yes", "No", "Yes", "No", "Yes")),
          notes = c("Note: Entries are unstandardized coefficients from linear models with clustered standard errors. *p \\textless{} .05; **p \\textless{} .01; ***p \\textless{} .001."),
          notes.append = FALSE)
```


# Table A12: Regression results: dominance.

```{r tab_A12, warning = FALSE}
### Load data and transform data________________________________________________

# load candidate data
load(here("Data", "Candidate_data.RData"))

# remove observations without imagges, bad quality images, and images with text
df <- df %>% filter(image_missing == 0, image_bad == 0, image_text == 0)

# recode general election dependent variables
fv22 <- df %>% filter(election == "FV22") %>% 
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes)))

# recode local election dependent variables
kv21 <- df %>% filter(election == "KV21") %>%
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes))) 

# recombine data
df <- rbind(fv22, kv21)

# create second data set by removing observation with only one candidate on the ballot
df2 <- df %>% filter(no_candidates_on_ballot != 1)

# define names
trait <- "Trustworthiness"
trait2 <- "trustworthiness_pd"
election <- "Pooled data"

# define names
trait <- "Dominance"
trait2 <- "dominance_pd"
election <- "Pooled data"

# Define clustered standard error function
se_robust <- function(x) {
  
  coeftest(x, vcovCL = df2$name2)[, "Std. Error"]
}



### Specify Models______________________________________________________________

# Ballot percentile rank - bivariate
model1a <- lm(formula = paste0("percentile_rank_std ~ ", trait2), data = df2)

# Ballot percentile rank + controls 
model1b <- lm(formula = paste0("percentile_rank_std ~ ", trait2, "+ age + gender + education"), data = df2)

# Ballot percentile rank + gender interaction
model1c <- lm(formula = paste0("percentile_rank_std ~ ", trait2, "*party_ideology"), data = df2)

# Ballot percentile rank + gender interaction + controls
model1d <- lm(formula = paste0("percentile_rank_std ~ ", trait2, "*party_ideology + age + gender + education"), data = df2)

# Ballot percentile rank + election interaction
model1e <- lm(formula = paste0("percentile_rank_std ~ ", trait2, "*election_type"), data = df2)

# Ballot percentile rank + election interaction + controls
model1f <- lm(formula = paste0("percentile_rank_std ~ ", trait2, "*election_type + age + gender + education"), data = df2)

# Log(personal votes) std - bivariate
model2a <- lm(formula = paste0("personal_votes_std ~ ", trait2), data = df)

# Log(personal votes) std + controls 
model2b <- lm(formula = paste0("personal_votes_std ~ ", trait2, "+ age + gender + education"), data = df)

# Log(personal votes) std + gender interaction
model2c <- lm(formula = paste0("personal_votes_std ~ ", trait2, "*party_ideology"), data = df)

# Log(personal votes) std + gender interaction + controls
model2d <- lm(formula = paste0("personal_votes_std ~ ", trait2, "*party_ideology + age + gender + education"), data = df)

# Log(personal votes) std + election interaction
model2e <- lm(formula = paste0("personal_votes_std ~ ", trait2, "*election_type"), data = df)

# Log(personal votes) std + election interaction + controls
model2f <- lm(formula = paste0("personal_votes_std ~ ", trait2, "*election_type + age + gender + education"), data = df)

# List models
models <- list(model1a, model1b, model1c, model1d, model1e, model1f, 
               model2a, model2b, model2c, model2d, model2e, model2f)

### Create table________________________________________________________________

# create and save table
stargazer(models,
          out = here("Tables", "Table_A12.tex"),
          header = FALSE,
          type = "latex",
          style = "apsr",
          dep.var.labels = c("Ballot Paper Placement", "Personal Votes"),
          order = c("dominance_pd", 
                    "dominance_pd:party_ideology",
                    "dominance_pd:election_typeLocal election",
                    "election_typeLocal election",
                    "party_ideology",
                    "gender", "age"),
          covariate.labels = c(trait, 
                               paste(trait, "x Right Wing Party"),
                               paste(trait, "x Local Election"),
                               "Local Election", "Right Wing Party", "Female", "Age"), 
          se = lapply(models, se_robust),
          no.space = FALSE,
          single.row = FALSE,
          df = FALSE,
          initial.zero = FALSE,
          star.cutoffs = c(0.05, 0.01, 0.001),
          align=FALSE,
          model.numbers = TRUE,
          omit.stat = c("ser"),
          omit = c("education"),
          add.lines = list(c("Education Control", "No", "Yes", "No", "Yes", "No", "Yes", "No", "Yes", "No", "Yes", "No", "Yes")),
          notes = c("Note: Entries are unstandardized coefficients from linear models with clustered standard errors. *p \\textless{} .05; **p \\textless{} .01; ***p \\textless{} .001."),
          notes.append = FALSE)

```


# Table A13: Regression results: using all facial traits simultaneously

```{r tab_A13, warning = FALSE}
### Load data and transform data________________________________________________

# load candidate data
load(here("Data", "Candidate_data.RData"))

# remove observations without imagges, bad quality images, and images with text
df <- df %>% filter(image_missing == 0, image_bad == 0, image_text == 0)

# recode general election dependent variables
fv22 <- df %>% filter(election == "FV22") %>% 
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes)))

# recode local election dependent variables
kv21 <- df %>% filter(election == "KV21") %>%
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes))) 

# recombine data
df <- rbind(fv22, kv21)

# create second data set by removing observation with only one candidate on the ballot
df2 <- df %>% filter(no_candidates_on_ballot != 1)

### Specify models______________________________________________________________

# specify models with ballot paper placement as DV
model_bpp1 <- lm(percentile_rank_std ~ attractiveness_pd + trustworthiness_pd + dominance_pd, data = df2)
model_bpp2 <- lm(percentile_rank_std ~ attractiveness_pd + trustworthiness_pd + dominance_pd + age + gender + education, data = df2)

# specify models with ballot paper placement as DV
model_pv1 <- lm(personal_votes_std ~ attractiveness_pd + trustworthiness_pd + dominance_pd, data = df)
model_pv2 <- lm(personal_votes_std ~ attractiveness_pd + trustworthiness_pd + dominance_pd + age + gender + education, data = df)

# Define clustered standard error function
se_robust <- function(x) {
  coeftest(x, vcovCL = df2$name2)[, "Std. Error"]
}

# list models
models <- list(model_bpp1, model_bpp2, model_pv1, model_pv2)

### Create table________________________________________________________________

# create and save table
stargazer(models,
          out = here("Tables", "Table_A13.tex"),
          header = FALSE,
          type = "latex",
          style = "apsr",
          dep.var.labels = c("Ballot Paper Placement", "Personal Votes"),
          covariate.labels = c("Attractiveness","Trustworthiness", "Dominance",
                               "Age", "Female"),
          se = lapply(models, se_robust),
          no.space = FALSE,
          single.row = FALSE,
          df = FALSE,
          initial.zero = FALSE,
          star.cutoffs = c(0.05, 0.01, 0.001),
          align=FALSE,
          model.numbers = TRUE,
          omit.stat = c("ser"),
          omit = c("education"),
          add.lines = list(c("Education Control", "No", "Yes", "No", "Yes")))
```

# Table A14: Regression results: facial traits as outcomes.

```{r tab_A14, warning = FALSE}
### Load data and transform data________________________________________________

# load candidate data
load(here("Data", "Candidate_data.RData"))

# remove observations without imagges, bad quality images, and images with text
df <- df %>% filter(image_missing == 0, image_bad == 0, image_text == 0)

# recode general election dependent variables
fv22 <- df %>% filter(election == "FV22") %>% 
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes)))

# recode local election dependent variables
kv21 <- df %>% filter(election == "KV21") %>%
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes))) 

# recombine data
df <- rbind(fv22, kv21)

# create second data set by removing observation with only one candidate on the ballot
df2 <- df %>% filter(no_candidates_on_ballot != 1)

### Specify Models______________________________________________________________

attr_model_1 <- lm(attractiveness_pd ~ age, data = df)
attr_model_2 <- lm(attractiveness_pd ~ gender, data = df)
attr_model_3 <- lm(attractiveness_pd ~ education, data = df)

trust_model_1 <- lm(trustworthiness_pd ~ age, data = df)
trust_model_2 <- lm(trustworthiness_pd ~ gender, data = df)
trust_model_3 <- lm(trustworthiness_pd ~ education, data = df)

dom_model_1 <- lm(dominance_pd ~ age, data = df)
dom_model_2 <- lm(dominance_pd ~ gender, data = df)
dom_model_3 <- lm(dominance_pd ~ education, data = df)


# Define clustered standard error function
se_robust <- function(x) {
  coeftest(x, vcovCL = df$name2)[, "Std. Error"]
}

# list models
models <- list(attr_model_1, attr_model_2, attr_model_3,
               trust_model_1, trust_model_2, trust_model_3,
               dom_model_1, dom_model_2, dom_model_3)

### Create table________________________________________________________________

# create and save table
stargazer(models,
          out = here("Tables", "Table_A14.tex"),
          header = FALSE,
          type = "latex",
          style = "apsr",
          dep.var.labels = c("Attractiveness", "Trustworthiness", "Dominance"),
          covariate.labels = c("Age", "Female", "Education: High School",
                               "Education: Vocational", "Education: Seminary", "Education: Undergraduate",
                               "Education: Graduate", "Education: Ph.d"),
          se = lapply(models, se_robust),
          no.space = FALSE,
          single.row = FALSE,
          df = FALSE,
          initial.zero = FALSE,
          star.cutoffs = c(0.05, 0.01, 0.001),
          align=FALSE,
          model.numbers = TRUE,
          omit.stat = c("ser"),
          notes = c("Note: Entries are unstandardized coefficients from linear models with clustered standard errors. *p \\textless{} .05; **p \\textless{} .01; ***p \\textless{} .001."),
          notes.append = FALSE)


```


# Table A15: Regression results: attractiveness - additional control for incumbency


```{r tab_A15, warning = FALSE}
### Load data and transform data________________________________________________

# load candidate data
load(here("Data", "Candidate_data.RData"))

# remove observations without imagges, bad quality images, and images with text
df <- df %>% filter(image_missing == 0, image_bad == 0, image_text == 0)

# recode general election dependent variables
fv22 <- df %>% filter(election == "FV22") %>% 
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes)))

# recode local election dependent variables
kv21 <- df %>% filter(election == "KV21") %>%
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes))) 

# recombine data
df <- rbind(fv22, kv21)

# create second data set by removing observation with only one candidate on the ballot
df2 <- df %>% filter(no_candidates_on_ballot != 1)


### Specify Models______________________________________________________________

# define names 
trait <- "Attractiveness"
trait2 <- "attractiveness_pd"
election <- "Pooled Data"

# Ballot percentile rank - bivariate
model1a <- lm(formula = paste0("percentile_rank_std ~ ", trait2, "+ incumbent"), data = df2)

# Ballot percentile rank + controls 
model1b <- lm(formula = paste0("percentile_rank_std ~ ", trait2, "+ incumbent + age + gender + education"), data = df2)

# Ballot percentile rank + gender interaction
model1c <- lm(formula = paste0("percentile_rank_std ~ ", trait2, "*gender + incumbent"), data = df2)

# Ballot percentile rank + gender interaction + controls
model1d <- lm(formula = paste0("percentile_rank_std ~ ", trait2, "*gender + incumbent + age + education"), data = df2)

# Ballot percentile rank + election interaction
model1e <- lm(formula = paste0("percentile_rank_std ~ ", trait2, "*election_type + incumbent"), data = df2)

# Ballot percentile rank + election interaction + controls
model1f <- lm(formula = paste0("percentile_rank_std ~ ", trait2, "*election_type + incumbent + age + gender + education"), data = df2)

# Log(personal votes) std - bivariate
model2a <- lm(formula = paste0("personal_votes_std ~ ", trait2, "+ incumbent"), data = df)

# Log(personal votes) std + controls 
model2b <- lm(formula = paste0("personal_votes_std ~ ", trait2, "+ incumbent+ age + gender + education"), data = df)

# Log(personal votes) std + gender interaction
model2c <- lm(formula = paste0("personal_votes_std ~ ", trait2, "*gender + incumbent"), data = df)

# Log(personal votes) std + gender interaction + controls
model2d <- lm(formula = paste0("personal_votes_std ~ ", trait2, "*gender + incumbent + age + education"), data = df)

# Log(personal votes) std + election interaction
model2e <- lm(formula = paste0("personal_votes_std ~ ", trait2, "*election_type + incumbent"), data = df)

# Log(personal votes) std + election interaction + controls
model2f <- lm(formula = paste0("personal_votes_std ~ ", trait2, "*election_type + incumbent + age + gender + education"), data = df)

# List models
models <- list(model1a, model1b, model1c, model1d, model1e, model1f, 
               model2a, model2b, model2c, model2d, model2e, model2f)

### Create table________________________________________________________________

stargazer(models,
          out = here("Tables", "Table_A15.tex"),
          header = FALSE,
          type = "latex",
          style = "apsr",
          dep.var.labels = c("Ballot Paper Placement", "Personal Votes"),
          order = c("attractiveness_pd", 
                    "attractiveness_pd:gender1",
                    "attractiveness_pd:election_typeLocal election",
                    "election_typeLocal election",
                    "incumbent1",
                    "gender", "age", "Education Control"),
          covariate.labels = c(trait, paste(trait, "x Female"),
                               paste(trait, "x Local Election"),
                               "Local Election", "Incumbent", "Female", "Age"), 
          se = lapply(models, se_robust),
          no.space = FALSE,
          single.row = FALSE,
          df = FALSE,
          initial.zero = FALSE,
          star.cutoffs = c(0.05, 0.01, 0.001),
          align=FALSE,
          model.numbers = TRUE,
          omit.stat = c("ser"),
          omit = c("education"),
          add.lines = list(c("Education Control", "No", "Yes", "No", "Yes", "No", "Yes", "No", "Yes", "No", "Yes", "No", "Yes")),
          notes = c("Note: Entries are unstandardized coefficients from linear models with clustered standard errors. *p \\textless{} .05; **p \\textless{} .01; ***p \\textless{} .001."),
          notes.append = FALSE)
```

# Table A16: Regression results: trustworthiness - additional control for incumbency.

```{r tab_A16, warning = FALSE}
### Load data and transform data________________________________________________

# load candidate data
load(here("Data", "Candidate_data.RData"))

# remove observations without imagges, bad quality images, and images with text
df <- df %>% filter(image_missing == 0, image_bad == 0, image_text == 0)

# recode general election dependent variables
fv22 <- df %>% filter(election == "FV22") %>% 
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes)))

# recode local election dependent variables
kv21 <- df %>% filter(election == "KV21") %>%
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes))) 

# recombine data
df <- rbind(fv22, kv21)

# create second data set by removing observation with only one candidate on the ballot
df2 <- df %>% filter(no_candidates_on_ballot != 1)

### Specify models______________________________________________________________

# define names
trait <- "Trustworthiness"
trait2 <- "trustworthiness_pd"
election <- "Pooled data"

# Ballot percentile rank - bivariate
model1a <- lm(formula = paste0("percentile_rank_std ~ ", trait2, "+ incumbent"), data = df2)

# Ballot percentile rank + controls 
model1b <- lm(formula = paste0("percentile_rank_std ~ ", trait2, "+ incumbent + age + gender + education"), data = df2)

# Ballot percentile rank + gender interaction
model1c <- lm(formula = paste0("percentile_rank_std ~ ", trait2, "*gender + incumbent"), data = df2)

# Ballot percentile rank + gender interaction + controls
model1d <- lm(formula = paste0("percentile_rank_std ~ ", trait2, "*gender + incumbent + age + education"), data = df2)

# Ballot percentile rank + election interaction
model1e <- lm(formula = paste0("percentile_rank_std ~ ", trait2, "*election_type + incumbent"), data = df2)

# Ballot percentile rank + election interaction + controls
model1f <- lm(formula = paste0("percentile_rank_std ~ ", trait2, "*election_type + incumbent + age + gender + education"), data = df2)

# Log(personal votes) std - bivariate
model2a <- lm(formula = paste0("personal_votes_std ~ ", trait2, "+ incumbent"), data = df)

# Log(personal votes) std + controls 
model2b <- lm(formula = paste0("personal_votes_std ~ ", trait2, "+ incumbent+ age + gender + education"), data = df)

# Log(personal votes) std + gender interaction
model2c <- lm(formula = paste0("personal_votes_std ~ ", trait2, "*gender + incumbent"), data = df)

# Log(personal votes) std + gender interaction + controls
model2d <- lm(formula = paste0("personal_votes_std ~ ", trait2, "*gender + incumbent + age + education"), data = df)

# Log(personal votes) std + election interaction
model2e <- lm(formula = paste0("personal_votes_std ~ ", trait2, "*election_type + incumbent"), data = df)

# Log(personal votes) std + election interaction + controls
model2f <- lm(formula = paste0("personal_votes_std ~ ", trait2, "*election_type + incumbent + age + gender + education"), data = df)

# List models
models <- list(model1a, model1b, model1c, model1d, model1e, model1f, 
               model2a, model2b, model2c, model2d, model2e, model2f)

#### create table_______________________________________________________________

stargazer(models,
          out = here("Tables", "Table_A16.tex"),
          header = FALSE,
          type = "latex",
          style = "apsr",
          dep.var.labels = c("Ballot Paper Placement", "Personal Votes"),
          order = c("trustworthiness_pd", 
                    "trustworthiness_pd:gender1",
                    "trustworthiness_pd:election_typeLocal election",
                    "election_typeLocal election",
                    "incumbent1",
                    "gender", "age", "Education Control"),
          covariate.labels = c(trait, paste(trait, "x Female"),
                               paste(trait, "x Local Election"),
                               "Local Election", "Incumbent", "Female", "Age"), 
          se = lapply(models, se_robust),
          no.space = FALSE,
          single.row = FALSE,
          df = FALSE,
          initial.zero = FALSE,
          star.cutoffs = c(0.05, 0.01, 0.001),
          align=FALSE,
          model.numbers = TRUE,
          omit.stat = c("ser"),
          omit = c("education"),
          add.lines = list(c("Education Control", "No", "Yes", "No", "Yes", "No", "Yes", "No", "Yes", "No", "Yes", "No", "Yes")),
          notes = c("Note: Entries are unstandardized coefficients from linear models with clustered standard errors. *p \\textless{} .05; **p \\textless{} .01; ***p \\textless{} .001."),
          notes.append = FALSE)


```


# Table A17: Regression results: dominance - additional control for incumbency.

```{r tab_A17, warning = FALSE}
### Load data and transform data________________________________________________

# load candidate data
load(here("Data", "Candidate_data.RData"))

# remove observations without imagges, bad quality images, and images with text
df <- df %>% filter(image_missing == 0, image_bad == 0, image_text == 0)

# recode general election dependent variables
fv22 <- df %>% filter(election == "FV22") %>% 
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes)))

# recode local election dependent variables
kv21 <- df %>% filter(election == "KV21") %>%
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes))) 

# recombine data
df <- rbind(fv22, kv21)

# create second data set by removing observation with only one candidate on the ballot
df2 <- df %>% filter(no_candidates_on_ballot != 1)


### Specify models______________________________________________________________

# define names
trait <- "Dominance"
trait2 <- "dominance_pd"
election <- "Pooled data"

# Ballot percentile rank - bivariate
model1a <- lm(formula = paste0("percentile_rank_std ~ ", trait2, "+ incumbent"), data = df2)

# Ballot percentile rank + controls 
model1b <- lm(formula = paste0("percentile_rank_std ~ ", trait2, "+ incumbent + age + gender + education"), data = df2)

# Ballot percentile rank + gender interaction
model1c <- lm(formula = paste0("percentile_rank_std ~ ", trait2, "*party_ideology + incumbent"), data = df2)

# Ballot percentile rank + gender interaction + controls
model1d <- lm(formula = paste0("percentile_rank_std ~ ", trait2, "*party_ideology + incumbent + age + gender + education"), data = df2)


# Ballot percentile rank + election interaction
model1e <- lm(formula = paste0("percentile_rank_std ~ ", trait2, "*election_type + incumbent"), data = df2)


# Ballot percentile rank + election interaction + controls
model1f <- lm(formula = paste0("percentile_rank_std ~ ", trait2, "*election_type + incumbent + age + gender + education"), data = df2)

# Log(personal votes) std - bivariate
model2a <- lm(formula = paste0("personal_votes_std ~ ", trait2, "+ incumbent"), data = df)

# Log(personal votes) std + controls 
model2b <- lm(formula = paste0("personal_votes_std ~ ", trait2, "+ incumbent + age + gender + education"), data = df)

# Log(personal votes) std + gender interaction
model2c <- lm(formula = paste0("personal_votes_std ~ ", trait2, "*party_ideology + incumbent"), data = df)

# Log(personal votes) std + gender interaction + controls
model2d <- lm(formula = paste0("personal_votes_std ~ ", trait2, "*party_ideology + incumbent + age + gender + education"), data = df)

# Log(personal votes) std + election interaction
model2e <- lm(formula = paste0("personal_votes_std ~ ", trait2, "*election_type + incumbent"), data = df)

# Log(personal votes) std + election interaction + controls
model2f <- lm(formula = paste0("personal_votes_std ~ ", trait2, "*election_type + incumbent + age + gender + education"), data = df)


# List models
models <- list(model1a, model1b, model1c, model1d, model1e, model1f, 
               model2a, model2b, model2c, model2d, model2e, model2f)


### Create table________________________________________________________________

stargazer(models,
          out = here("Tables", "Table_A17.tex"),
          header = FALSE,
          type = "latex",
          style = "apsr",
          dep.var.labels = c("Ballot Paper Placement", "Personal Votes"),
          order = c("dominance_pd", 
                    "dominance_pd:party_ideology",
                    "dominance_pd:election_typeLocal election",
                    "election_typeLocal election",
                    "party_ideology", "incumbent1",
                    "gender", "age"),
          covariate.labels = c(trait, 
                               paste(trait, "x Right Wing Party"),
                               paste(trait, "x Local Election"),
                               "Local Election", "Right Wing Party", "Incumbent", "Female", "Age"), 
          se = lapply(models, se_robust),
          no.space = FALSE,
          single.row = FALSE,
          df = FALSE,
          initial.zero = FALSE,
          star.cutoffs = c(0.05, 0.01, 0.001),
          align=FALSE,
          model.numbers = TRUE,
          omit.stat = c("ser"),
          omit = c("education"),
          add.lines = list(c("Education Control", "No", "Yes", "No", "Yes", "No", "Yes", "No", "Yes", "No", "Yes", "No", "Yes")),
          notes = c("Note: Entries are unstandardized coefficients from linear models with clustered standard errors. *p \\textless{} .05; **p \\textless{} .01; ***p \\textless{} .001."),
          notes.append = FALSE)

```



# Table A18: Regression results: Party fixed effects.

```{r tab_A18}
### Load data and transform data________________________________________________

# load candidate data
load(here("Data", "Candidate_data.RData"))

# remove observations without imagges, bad quality images, and images with text
df <- df %>% filter(image_missing == 0, image_bad == 0, image_text == 0)

# recode general election dependent variables
fv22 <- df %>% filter(election == "FV22") %>% 
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes)))

# recode local election dependent variables
kv21 <- df %>% filter(election == "KV21") %>%
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes))) 

# recombine data
df <- rbind(fv22, kv21)

# create second data set by removing observation with only one candidate on the ballot
df2 <- df %>% filter(no_candidates_on_ballot != 1)


### Specify models______________________________________________________________

# # specify models with ballot paper placement as DV and party fixed effects
model_bpp_attr <- feols(percentile_rank_std ~ attractiveness_pd | party , data = df2)
model_bpp_trust <- feols(percentile_rank_std ~ trustworthiness_pd | party , data = df2)
model_bpp_dom <- feols(percentile_rank_std ~ dominance_pd | party, data = df2)
model_bpp_all <- feols(percentile_rank_std ~ attractiveness_pd + trustworthiness_pd + dominance_pd | party, data = df2)

# # specify models with personal votes as DV and party fixed effects
model_pv_attr <- feols(personal_votes_std ~ attractiveness_pd | party, data = df)
model_pv_trust <- feols(personal_votes_std ~ trustworthiness_pd | party, data = df)
model_pv_dom <- feols(personal_votes_std ~ dominance_pd | party, data = df)
model_pv_all <- feols(personal_votes_std ~ attractiveness_pd + trustworthiness_pd + dominance_pd | party, data = df)


### Create table________________________________________________________________

# list models
models <- list(model_bpp_attr, model_bpp_trust, model_bpp_dom, model_bpp_all,
               model_pv_attr, model_pv_trust, model_pv_dom, model_pv_all)


# set dictionary
setFixest_dict(c(attractiveness_pd = "Attractivness", 
                 trustworthiness_pd = "Trustworthiness",
                 dominance_pd = "Dominance", 
                 percentile_rank_std = "Ballot Paper Placement",
                 personal_votes_std = "Personal Votes"
                 ))

setFixest_etable(style.tex = style.tex("aer"), 
                 fitstat = ~ r2 + n)

# create and save atble
etable(models, tex = TRUE,
       signif.code = c(" *"=.05, " **"=.01, " ***"=.001),
       file = here("Tables", "Table_A18.tex")
       )
```

# Table A19: Regression results: Age as a moderator.

```{r tab_A19, warning = FALSE}
### Load data and transform data________________________________________________

# load candidate data
load(here("Data", "Candidate_data.RData"))

# remove observations without imagges, bad quality images, and images with text
df <- df %>% filter(image_missing == 0, image_bad == 0, image_text == 0)

# recode general election dependent variables
fv22 <- df %>% filter(election == "FV22") %>% 
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes)))

# recode local election dependent variables
kv21 <- df %>% filter(election == "KV21") %>%
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes))) 

# recombine data
df <- rbind(fv22, kv21)

# create second data set by removing observation with only one candidate on the ballot
df2 <- df %>% filter(no_candidates_on_ballot != 1)

### Specify models______________________________________________________________

# specify models with ballot paper placement as DV
model_bpp_attr_1 <- lm(percentile_rank_std ~ attractiveness_pd*age, data = df2)
model_bpp_attr_2 <- lm(percentile_rank_std ~ attractiveness_pd*age + gender + education, data = df2)

model_bpp_trust_1 <- lm(percentile_rank_std ~ trustworthiness_pd*age, data = df2)
model_bpp_trust_2 <- lm(percentile_rank_std ~ trustworthiness_pd*age + gender + education, data = df2)

model_bpp_dom_1 <- lm(percentile_rank_std ~ dominance_pd*age, data = df2)
model_bpp_dom_2 <- lm(percentile_rank_std ~ dominance_pd*age + gender + education, data = df2)


# specify models with ballot paper placement as DV
model_pv_attr_1 <- lm(personal_votes_std ~ attractiveness_pd*age, data = df)
model_pv_attr_2 <- lm(personal_votes_std ~ attractiveness_pd*age + gender + education, data = df)

model_pv_trust_1 <- lm(personal_votes_std ~ trustworthiness_pd*age, data = df)
model_pv_trust_2 <- lm(personal_votes_std ~ trustworthiness_pd*age + gender + education, data = df)

model_pv_dom_1 <- lm(personal_votes_std ~ dominance_pd*age, data = df)
model_pv_dom_2 <- lm(personal_votes_std ~ dominance_pd*age + gender + education, data = df)

### Create table________________________________________________________________

# Define clustered standard error function
se_robust <- function(x) {
  coeftest(x, vcovCL = df2$name2)[, "Std. Error"]
}

# list models
models <- list(model_bpp_attr_1, model_bpp_attr_2,
               model_bpp_trust_1, model_bpp_trust_2,
               model_bpp_dom_1, model_bpp_dom_2,
               model_pv_attr_1, model_pv_attr_2,
               model_pv_trust_1, model_pv_trust_2,
               model_pv_dom_1, model_pv_dom_2)

# create and save table
stargazer(models,
          out = here("Tables", "Table_A19.tex"),
          header = FALSE,
          type = "latex",
          style = "apsr",
          dep.var.labels = c("Ballot Paper Placement", "Personal Votes"),
          covariate.labels = c("Attractiveness","Trustworthiness", "Dominance",
                               "Age", "Female",
                               "Attracivness X Age",
                               "Trustworthiness X Age",
                               "Dominance X Age"),
          se = lapply(models, se_robust),
          no.space = FALSE,
          single.row = FALSE,
          df = FALSE,
          initial.zero = FALSE,
          star.cutoffs = c(0.05, 0.01, 0.001),
          align=FALSE,
          model.numbers = TRUE,
          omit.stat = c("ser"),
          omit = c("education"),
          add.lines = list(c("Education Control", "No", "Yes", "No", "Yes", "No", "Yes", "No", "Yes", "No", "Yes", "No", "Yes")))
```


# Table A20: Regression results: Ethnicity as moderator

```{r tab_A20, warning = FALSE}
### Load data and transform data________________________________________________

# load candidate data
load(here("Data", "Candidate_data.RData"))

# remove observations without imagges, bad quality images, and images with text
df <- df %>% filter(image_missing == 0, image_bad == 0, image_text == 0)

# recode general election dependent variables
fv22 <- df %>% filter(election == "FV22") %>% 
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes)))

# recode local election dependent variables
kv21 <- df %>% filter(election == "KV21") %>%
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes))) 

# recombine data
df <- rbind(fv22, kv21)

# create second data set by removing observation with only one candidate on the ballot
df2 <- df %>% filter(no_candidates_on_ballot != 1)

### Specify models______________________________________________________________

# specify models with ballot paper placement as DV
model_bpp_attr_1 <- lm(percentile_rank_std ~ attractiveness_pd*ethnicity, data = df2)
model_bpp_attr_2 <- lm(percentile_rank_std ~ attractiveness_pd*ethnicity + age + gender + education, data = df2)

model_bpp_trust_1 <- lm(percentile_rank_std ~ trustworthiness_pd*ethnicity, data = df2)
model_bpp_trust_2 <- lm(percentile_rank_std ~ trustworthiness_pd*ethnicity + age + gender + education, data = df2)

model_bpp_dom_1 <- lm(percentile_rank_std ~ dominance_pd*ethnicity, data = df2)
model_bpp_dom_2 <- lm(percentile_rank_std ~ dominance_pd*ethnicity + age + gender + education, data = df2)


# specify models with ballot paper placement as DV
model_pv_attr_1 <- lm(personal_votes_std ~ attractiveness_pd*ethnicity, data = df)
model_pv_attr_2 <- lm(personal_votes_std ~ attractiveness_pd*ethnicity + age + gender + education, data = df)

model_pv_trust_1 <- lm(personal_votes_std ~ trustworthiness_pd*ethnicity, data = df)
model_pv_trust_2 <- lm(personal_votes_std ~ trustworthiness_pd*ethnicity + age + gender + education, data = df)

model_pv_dom_1 <- lm(personal_votes_std ~ dominance_pd*ethnicity, data = df)
model_pv_dom_2 <- lm(personal_votes_std ~ dominance_pd*ethnicity + age + gender + education, data = df)

# Define clustered standard error function
se_robust <- function(x) {
  coeftest(x, vcovCL = df2$name2)[, "Std. Error"]
}

# list models
models <- list(model_bpp_attr_1, model_bpp_attr_2,
               model_bpp_trust_1, model_bpp_trust_2,
               model_bpp_dom_1, model_bpp_dom_2,
               model_pv_attr_1, model_pv_attr_2,
               model_pv_trust_1, model_pv_trust_2,
               model_pv_dom_1, model_pv_dom_2)

### Create table________________________________________________________________

stargazer(models,
          out = here("Tables", "Table_A20.tex"),
          header = FALSE,
          type = "latex",
          style = "apsr",
          dep.var.labels = c("Ballot Paper Placement", "Personal Votes"),
          covariate.labels = c("Attractiveness","Trustworthiness", "Dominance",
                               "Ethnicity: Asian", "Ethnicity: Middle-Eastern", "Ethnicity: White",
                               "Age", "Female",
                               "Attracivness X Asian",
                               "Attractivness X Middle-Eastern",
                               "Attractivness X White",
                               "Trustworthiness X Asian",
                               "Trustworthiness X Middle-Eastern",
                               "Trustworthiness X White",
                               "Dominance X Asian",
                               "Dominance X Middle-Eastern",
                               "Dominance X White"),
          se = lapply(models, se_robust),
          no.space = FALSE,
          single.row = FALSE,
          df = FALSE,
          initial.zero = FALSE,
          star.cutoffs = c(0.05, 0.01, 0.001),
          align=FALSE,
          model.numbers = TRUE,
          omit.stat = c("ser"),
          omit = c("education"),
          add.lines = list(c("Education Control", "No", "Yes", "No", "Yes", "No", "Yes", "No", "Yes", "No", "Yes", "No", "Yes")))


```


# Table A21: Summary statistics: One Million Impression data.

```{r tab_A21, message = FALSE}
# load OMI data
df <- rio::import(here("Data", "OMI_attribute_means.csv"))

# images of children not used for training models
children <- c("8", "12", "37", "47", "57", "59", "60", "61", "62", "73",
            "81", "98", "114", "127", "129", "136", "152", "157", "164", "172",
            "174", "194", "214", "218", "226", "231", "232", "235", "243",
            "252", "257", "261", "262", "254", "286", "298", "317", "319", "327",
            "329", "336", "343", "344", "353", "357", "359", "397", "406", 
            "418", "419", "425", "429", "430", "431", "440", "442", "449",
            "453", "470", "489", "493", "506", "532", "532", "576", "578",
            "619", "620", "628", "629", "632", "634", "639", "650", "660",
            "669", "673", "675", "687", "690", "693", "700", "703", "705",
            "718", "721", "722", "724", "725", "732", "735", "744", "747",
            "760", "761", "769", "786", "789", "795", "799", "817", "822",
            "844", "845", "858", "871", "873", "876", "882", "890", "894",
            "896", "919", "923", "941", "948", "951", "968", "971", "990",
            "993", "994", "997", "1004") 


df <- df %>% filter(!stimulus %in% children) %>%
  mutate(
    ethnicity = colnames(.[24:30])[max.col(.[24:30], "first")],
    gender = ifelse(gender>50, 0, 1),
    attractive = attractive / 10,
    dominant = dominant / 10,
    trustworthy = trustworthy / 10) %>% 
  select(age, attractive, dominant, trustworthy, gender, ethnicity)
  
# create data frame 
variables <- c("Age", "Attractive", "Trustworthy", "Dominant", "Gender", 
               "Male", "Female", "Ethnicity", "White", "Middle-eastern",
               "Hispanic", "Asian", "Black", "Native", "Islander")

data <- data.frame(
  Variable = variables,
  N = rep("", length(variables)),
  Mean = rep("", length(variables)),
  SD = rep("", length(variables)),
  Min = rep("", length(variables)),
  p25 = rep("", length(variables)),
  Median = rep("", length(variables)),
  p75 = rep("", length(variables)),
  max = rep("", length(variables))
)

data[1,2] <- length(df$age)
data[1,3] <- round(mean(df$age),2)
data[1,4] <- round(sd(df$age),2)
data[1,5] <- round(min(df$age),2)
data[1,6] <- round(quantile(df$age, 0.25, names = FALSE), 2)
data[1,7] <- round(quantile(df$age, 0.5, names = FALSE), 2)
data[1,8] <- round(quantile(df$age, 0.75, names = FALSE), 2)
data[1,9] <- round(max(df$age),2)

data[2,2] <- length(df$attractive)
data[2,3] <- round(mean(df$attractive),2)
data[2,4] <- round(sd(df$attractive),2)
data[2,5] <- round(min(df$attractive),2)
data[2,6] <- round(quantile(df$attractive, 0.25, names = FALSE), 2)
data[2,7] <- round(quantile(df$attractive, 0.5, names = FALSE), 2)
data[2,8] <- round(quantile(df$attractive, 0.75, names = FALSE), 2)
data[2,9] <- round(max(df$attractive),2)

data[3,2] <- length(df$trustworthy)
data[3,3] <- round(mean(df$trustworthy),2)
data[3,4] <- round(sd(df$trustworthy),2)
data[3,5] <- round(min(df$trustworthy),2)
data[3,6] <- round(quantile(df$trustworthy, 0.25, names = FALSE), 2)
data[3,7] <- round(quantile(df$trustworthy, 0.5, names = FALSE), 2)
data[3,8] <- round(quantile(df$trustworthy, 0.75, names = FALSE), 2)
data[3,9] <- round(max(df$trustworthy),2)

data[4,2] <- length(df$dominant)
data[4,3] <- round(mean(df$dominant),2)
data[4,4] <- round(sd(df$dominant),2)
data[4,5] <- round(min(df$dominant),2)
data[4,6] <- round(quantile(df$dominant, 0.25, names = FALSE), 2)
data[4,7] <- round(quantile(df$dominant, 0.5, names = FALSE), 2)
data[4,8] <- round(quantile(df$dominant, 0.75, names = FALSE), 2)
data[4,9] <- round(max(df$dominant),2)

data[6,2] <- length(df$gender[df$gender == 0])
data[7,2] <- length(df$gender[df$gender == 1])
data[9,2] <- length(df$ethnicity[df$ethnicity == "white"])
data[10,2] <- length(df$ethnicity[df$ethnicity == "middle-eastern"])
data[11,2] <- length(df$ethnicity[df$ethnicity == "hispanic"])
data[12,2] <- length(df$ethnicity[df$ethnicity == "asian"])
data[13,2] <- length(df$ethnicity[df$ethnicity == "black"])
data[14,2] <- length(df$ethnicity[df$ethnicity == "native"])
data[15,2] <- length(df$ethnicity[df$ethnicity == "islander"])


# create table
table <- data %>%
  kbl(format = "latex", booktabs = TRUE, align = "lcccccccc",
      col.names = c("", "N", "Mean", "St. Dev.", "Min", "Q1","Median", "Q3", "Max")) %>%
  kable_classic() %>% gsub("\\\\addlinespace", "", .)

# save table
writeLines(table, here("Tables", "Table_A21.tex"))

# print table
table %>% cat()
```

# Table A22: Summary statistics: Chicago Face Database data 

```{r tab_A22, message = FALSE}
# load data
df <- rio::import_list(here("Data", "CFD 3.0 Norming Data and Codebook.xlsx"))

df <- df$`CFD U.S. Norming Data`

df <- df %>% 
  mutate(ethnicity = EthnicitySelf,
         gender = ifelse(GenderSelf == "F", 1, 0),
         age = AgeRated,
         attractive = Attractive,
         trustworthy = Trustworthy,
         dominant = Dominant) %>%
  select(ethnicity, gender, age, attractive, trustworthy, dominant)


# create data frame 
variables <- c("Age", "Attractive", "Trustworthy", "Dominant", "Gender", 
               "Male", "Female", "Ethnicity", "Asian", "Black",
               "Latino", "White")

data <- data.frame(
  Variable = variables,
  N = rep("", length(variables)),
  Mean = rep("", length(variables)),
  SD = rep("", length(variables)),
  Min = rep("", length(variables)),
  p25 = rep("", length(variables)),
  Median = rep("", length(variables)),
  p75 = rep("", length(variables)),
  max = rep("", length(variables))
)

data[1,2] <- length(df$age)
data[1,3] <- round(mean(df$age),2)
data[1,4] <- round(sd(df$age),2)
data[1,5] <- round(min(df$age),2)
data[1,6] <- round(quantile(df$age, 0.25, names = FALSE), 2)
data[1,7] <- round(quantile(df$age, 0.5, names = FALSE), 2)
data[1,8] <- round(quantile(df$age, 0.75, names = FALSE), 2)
data[1,9] <- round(max(df$age),2)

data[2,2] <- length(df$attractive)
data[2,3] <- round(mean(df$attractive),2)
data[2,4] <- round(sd(df$attractive),2)
data[2,5] <- round(min(df$attractive),2)
data[2,6] <- round(quantile(df$attractive, 0.25, names = FALSE), 2)
data[2,7] <- round(quantile(df$attractive, 0.5, names = FALSE), 2)
data[2,8] <- round(quantile(df$attractive, 0.75, names = FALSE), 2)
data[2,9] <- round(max(df$attractive),2)

data[3,2] <- length(df$trustworthy)
data[3,3] <- round(mean(df$trustworthy),2)
data[3,4] <- round(sd(df$trustworthy),2)
data[3,5] <- round(min(df$trustworthy),2)
data[3,6] <- round(quantile(df$trustworthy, 0.25, names = FALSE), 2)
data[3,7] <- round(quantile(df$trustworthy, 0.5, names = FALSE), 2)
data[3,8] <- round(quantile(df$trustworthy, 0.75, names = FALSE), 2)
data[3,9] <- round(max(df$trustworthy),2)

data[4,2] <- length(df$dominant)
data[4,3] <- round(mean(df$dominant),2)
data[4,4] <- round(sd(df$dominant),2)
data[4,5] <- round(min(df$dominant),2)
data[4,6] <- round(quantile(df$dominant, 0.25, names = FALSE), 2)
data[4,7] <- round(quantile(df$dominant, 0.5, names = FALSE), 2)
data[4,8] <- round(quantile(df$dominant, 0.75, names = FALSE), 2)
data[4,9] <- round(max(df$dominant),2)

data[6,2] <- length(df$gender[df$gender == 0])
data[7,2] <- length(df$gender[df$gender == 1])
data[9,2] <- length(df$ethnicity[df$ethnicity == "A"])
data[10,2] <- length(df$ethnicity[df$ethnicity == "B"])
data[11,2] <- length(df$ethnicity[df$ethnicity == "L"])
data[12,2] <- length(df$ethnicity[df$ethnicity == "W"])

# create table
table <- data %>%
  kbl(format = "latex", booktabs = TRUE, align = "lcccccccc",
      col.names = c("", "N", "Mean", "St. Dev.", "Min", "Q1","Median", "Q3", "Max")) %>%
  kable_classic() %>% gsub("\\\\addlinespace", "", .)

# save table
writeLines(table, here("Tables", "Table_A22.tex"))

# print table
table %>% cat()
```

# Table A23: Regression results, kernel-regularized least squares: attractiveness. Outcome: ballot paper placement.


```{r tab_A23_1, eval=FALSE}
## KRLS takes a long time to run. Therefore, we do not run this chunk.
## Instead we load the saved result object in the next chunk and create the 
## table from that.

# load candidate data
load(here("Data", "Candidate_data.RData"))

# remove observations without images, bad quality images, and images with text
df <- df %>% filter(image_missing == 0, image_bad == 0, image_text == 0)

# recode general election dependent variables
fv22 <- df %>% filter(election == "FV22") %>% 
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes)))

# recode local election dependent variables
kv21 <- df %>% filter(election == "KV21") %>%
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes))) 

# recombine data
df <- rbind(fv22, kv21)

# create second data set by removing observation with only one candidate on the ballot
df2 <- df %>% filter(no_candidates_on_ballot != 1)

# create education dummies
df2 <- fastDummies::dummy_cols(df2, select_columns = "education")

df2 <- df2 %>% rename("education_Primary_School" = "education_Primary School",
                    "education_High_School" = "education_High School",
                    "education_Phd" = "education_Ph.d")
# set names
trait2 <- "attractiveness_pd"

d <- df2  %>%
  mutate(gender = as.numeric(gender),
         election_type = ifelse(election_type == "Local election", 1, 0)) %>%
  mutate(election_type = as.numeric(as.character(election_type))) 

d <- d[, c("percentile_rank_std", trait2, "gender", "age",
           "education_Primary_School", "education_High_School", "education_Vocational",
           "education_Seminary", "education_Undergraduate", "education_Graduate", "education_Phd")]
d <- d[complete.cases(d), ]

# run krls - this takes a long time to run
out <- krls(y = d[,1], X = d[,-1], derivative = TRUE, binary = TRUE, print.level = 0)
summary(out)

save(out, file=here("Data", "KRLS.Attractiveness.Ballot.Position.RData"))
```


```{r tab_A23_2}
# load krls results
load(here("Data", "KRLS.Attractiveness.Ballot.Position.RData"))
res <- summary(out)

# set names
trait <- "Attractiveness"
trait2 <- "attractiveness_pd"


# data frame for results
ame <- c("attractiveness_pd", "gender", "age", "education_Primary_School*", 
         "education_High_School*", "education_Vocational*", "education_Seminary*",
         "education_Undergraduate*", "education_Graduate*", "education_Phd*")

data <- data.frame(
  ame,
  Est = rep(NA, length(ame)),
  Std_error = rep(NA, length(ame)),
  t_value = rep(NA, length(ame)),
  Pr = rep(NA, length(ame))
)

# add summary results to data table
for(i in 1:10) {
  data[i,2] <- round(res$coefficients[i], 3)
  data[i,3] <- round(res$coefficients[i, "Std. Error"], 3)
  data[i,4] <- round(res$coefficients[i, "t value"], 3)
  data[i,5] <- round(res$coefficients[i, "Pr(>|t|)"], 3)
}

# create table
rownames(data) <- c(trait, "Female", "Age", 
                      "Education: Primary School", "Education: High School", "Education: Vocational", 
                      "Education: Seminary", "Education: Undergraduate", "Education: Graduate", "Education: Phd")
# remove first column
data <- data[, -1]

table <- data %>%
  kbl(format = "latex", booktabs = TRUE, align = "cccc",
      col.names = c("Estimate", "Std. error", "t-value", "P(>|t|)")) %>%
  kable_styling()

# fix problem with special latex characters
table <- gsub("P\\([^)]*\\|t\\|\\)", "P(\\\\textgreater{}\\\\textbar{}t\\\\textbar{})", table, perl = TRUE)

# save table
writeLines(table, here("Tables", "Table_A23.tex"))

# print table
table %>% cat()
```

# Table A24: Regression results, kernel-regularized least squares: attractiveness. Outcome: personal votes

```{r tab_A24_1, eval = FALSE}
## KRLS takes a long time to run. Therefore, we do not run this chunk.
## Instead we load the saved result object in the next chunk and create the 
## table from that.

# load candidate data
load(here("Data", "Candidate_data.RData"))

# remove observations without images, bad quality images, and images with text
df <- df %>% filter(image_missing == 0, image_bad == 0, image_text == 0)

# recode general election dependent variables
fv22 <- df %>% filter(election == "FV22") %>% 
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes)))

# recode local election dependent variables
kv21 <- df %>% filter(election == "KV21") %>%
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes))) 

# recombine data
df <- rbind(fv22, kv21)

# create second data set by removing observation with only one candidate on the ballot
df2 <- df %>% filter(no_candidates_on_ballot != 1)

# create education dummies
df <- fastDummies::dummy_cols(df, select_columns = "education")

df <- df %>% rename("education_Primary_School" = "education_Primary School",
                    "education_High_School" = "education_High School",
                    "education_Phd" = "education_Ph.d")

# set names
trait <- "Attractiveness"
trait2 <- "attractiveness_pd"


d <- df  %>%
  mutate(gender = as.numeric(gender),
         election_type = ifelse(election_type == "Local election", 1, 0)) %>%
  mutate(election_type = as.numeric(as.character(election_type))) 

d <- d[, c("personal_votes_std", trait2, "gender", "age",
           "education_Primary_School", "education_High_School", "education_Vocational",
           "education_Seminary", "education_Undergraduate", "education_Graduate", "education_Phd")]
d <- d[complete.cases(d), ]

out <- krls(y = d[,1], X = d[,-1], derivative = TRUE, binary = TRUE, print.level = 0)
summary(out)

save(out, file=here("Data", "KRLS.Attractiveness.Personal.Votes.RData"))
```


```{r tab_24_2}
# load KRLS results
load(here("Data", "KRLS.Attractiveness.Personal.Votes.RData"))
res <- summary(out)

# set names
trait <- "Attractiveness"
trait2 <- "attractiveness_pd"


# data frame for results
ame <- c("attractiveness_pd", "gender", "age", "education_Primary_School*", 
         "education_High_School*", "education_Vocational*", "education_Seminary*",
         "education_Undergraduate*", "education_Graduate*", "education_Phd*")

data <- data.frame(
  ame,
  Est = rep(NA, length(ame)),
  Std_error = rep(NA, length(ame)),
  t_value = rep(NA, length(ame)),
  Pr = rep(NA, length(ame))
)

# add summary results to data table
for(i in 1:10) {
  data[i,2] <- round(res$coefficients[i], 3)
  data[i,3] <- round(res$coefficients[i, "Std. Error"], 3)
  data[i,4] <- round(res$coefficients[i, "t value"], 3)
  data[i,5] <- round(res$coefficients[i, "Pr(>|t|)"], 3)
}

# create table
data <- data[, -1]

rownames(data) <- c(trait, "Female", "Age", 
                    "Education: Primary School", "Education: High School", "Education: Vocational", 
                    "Education: Seminary", "Education: Undergraduate", "Education: Graduate", "Education: Phd")


table <- data %>%
  kbl(format = "latex", booktabs = TRUE, align = "cccc",
      col.names = c("Estimate", "Std. error", "t-value", "P(>|t|)")) %>%
  kable_styling()

# fix problem with special latex characters
table <- gsub("P\\([^)]*\\|t\\|\\)", "P(\\\\textgreater{}\\\\textbar{}t\\\\textbar{})", table, perl = TRUE)

# save table
writeLines(table, here("Tables", "Table_A24.tex"))

# print table
table %>% cat()
```


# Table A25: Regression results, kernel-regularized least squares: trustworthiness. Outcome: ballot paper placement

```{r tab_A25_1, eval = FALSE}
## KRLS takes a long time to run. Therefore, we do not run this chunk.
## Instead we load the saved result object in the next chunk and create the 
## table from that.

# load candidate data
load(here("Data", "Candidate_data.RData"))

# remove observations without images, bad quality images, and images with text
df <- df %>% filter(image_missing == 0, image_bad == 0, image_text == 0)

# recode general election dependent variables
fv22 <- df %>% filter(election == "FV22") %>% 
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes)))

# recode local election dependent variables
kv21 <- df %>% filter(election == "KV21") %>%
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes))) 

# recombine data
df <- rbind(fv22, kv21)

# create second data set by removing observation with only one candidate on the ballot
df2 <- df %>% filter(no_candidates_on_ballot != 1)

# create education dummies
df2 <- fastDummies::dummy_cols(df2, select_columns = "education")

df2 <- df2 %>% rename("education_Primary_School" = "education_Primary School",
                    "education_High_School" = "education_High School",
                    "education_Phd" = "education_Ph.d")

# set names 
trait <- "Trustworthiness"
trait2 <- "trustworthiness_pd"

d <- df2  %>%
  mutate(gender = as.numeric(gender),
         election_type = ifelse(election_type == "Local election", 1, 0)) %>%
  mutate(election_type = as.numeric(as.character(election_type))) 

d <- d[, c("percentile_rank_std", trait2, "gender", "age",
           "education_Primary_School", "education_High_School", "education_Vocational",
           "education_Seminary", "education_Undergraduate", "education_Graduate", "education_Phd")]
d <- d[complete.cases(d), ]

# run krls - this takes a long time to run
out <- krls(y = d[,1], X = d[,-1], derivative = TRUE, binary = TRUE, print.level = 0)
summary(out)

save(out, file=here("Data", "KRLS.Trustworthiness.Ballot.Position.RData"))
```


```{r tab_A25_2}
# load krls results
load(here("Data", "KRLS.Trustworthiness.Ballot.Position.RData"))
res <- summary(out)

# set names
trait <- "Trustworthiness"
trait2 <- "trustworthiness_pd"


# data frame for results
ame <- c("trustworthiness_pd", "gender", "age", "education_Primary_School*", 
         "education_High_School*", "education_Vocational*", "education_Seminary*",
         "education_Undergraduate*", "education_Graduate*", "education_Phd*")

data <- data.frame(
  ame,
  Est = rep(NA, length(ame)),
  Std_error = rep(NA, length(ame)),
  t_value = rep(NA, length(ame)),
  Pr = rep(NA, length(ame))
)

# add summary results to data table
for(i in 1:10) {
  data[i,2] <- round(res$coefficients[i], 3)
  data[i,3] <- round(res$coefficients[i, "Std. Error"], 3)
  data[i,4] <- round(res$coefficients[i, "t value"], 3)
  data[i,5] <- round(res$coefficients[i, "Pr(>|t|)"], 3)
}

# create table
data <- data[, -1]

# create table
rownames(data) <- c(trait, "Female", "Age", 
                    "Education: Primary School", "Education: High School", "Education: Vocational", 
                    "Education: Seminary", "Education: Undergraduate", "Education: Graduate", "Education: Phd")


table <- data %>%
  kbl(format = "latex", booktabs = TRUE, align = "cccc",
      col.names = c("Estimate", "Std. error", "t-value", "P(>|t|)")) %>%
  kable_styling()

# fix problem with special latex characters
table <- gsub("P\\([^)]*\\|t\\|\\)", "P(\\\\textgreater{}\\\\textbar{}t\\\\textbar{})", table, perl = TRUE)

# save table
writeLines(table, here("Tables", "Table_A25.tex"))

# print table
table %>% cat()
```


# Table A26: Regression results, kernel-regularized least squares: trustworthiness. Outcome: personal votes

```{r tab_A26_1, eval = FALSE}
## KRLS takes a long time to run. Therefore, we do not run this chunk.
## Instead we load the saved result object in the next chunk and create the 
## table from that.

# load candidate data
load(here("Data", "Candidate_data.RData"))

# remove observations without images, bad quality images, and images with text
df <- df %>% filter(image_missing == 0, image_bad == 0, image_text == 0)

# recode general election dependent variables
fv22 <- df %>% filter(election == "FV22") %>% 
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes)))

# recode local election dependent variables
kv21 <- df %>% filter(election == "KV21") %>%
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes))) 

# recombine data
df <- rbind(fv22, kv21)

# create second data set by removing observation with only one candidate on the ballot
df2 <- df %>% filter(no_candidates_on_ballot != 1)

# create education dummies
df <- fastDummies::dummy_cols(df, select_columns = "education")

df <- df %>% rename("education_Primary_School" = "education_Primary School",
                    "education_High_School" = "education_High School",
                    "education_Phd" = "education_Ph.d")

# set names
trait <- "Trustworthiness"
trait2 <- "trustworthiness_pd"



d <- df  %>%
  mutate(gender = as.numeric(gender),
         election_type = ifelse(election_type == "Local election", 1, 0)) %>%
  mutate(election_type = as.numeric(as.character(election_type))) 

d <- d[, c("personal_votes_std", trait2, "gender", "age",
           "education_Primary_School", "education_High_School", "education_Vocational",
           "education_Seminary", "education_Undergraduate", "education_Graduate", "education_Phd")]
d <- d[complete.cases(d), ]

out <- krls(y = d[,1], X = d[,-1], derivative = TRUE, binary = TRUE, print.level = 0)
summary(out)

save(out, file=here("Data", "KRLS.Trustworthiness.Personal.Votes.RData"))
```


```{r tab_A26_2}
# load krls results
load(here("Data", "KRLS.Trustworthiness.Personal.Votes.RData"))
res <- summary(out)

# set names
trait <- "Trustworthiness"
trait2 <- "trustworthiness_pd"


# data frame for results
ame <- c("trustworthiness_pd", "gender", "age", "education_Primary_School*", 
         "education_High_School*", "education_Vocational*", "education_Seminary*",
         "education_Undergraduate*", "education_Graduate*", "education_Phd*")

data <- data.frame(
  ame,
  Est = rep(NA, length(ame)),
  Std_error = rep(NA, length(ame)),
  t_value = rep(NA, length(ame)),
  Pr = rep(NA, length(ame))
)

# add summary results to data table
for(i in 1:10) {
  data[i,2] <- round(res$coefficients[i], 3)
  data[i,3] <- round(res$coefficients[i, "Std. Error"], 3)
  data[i,4] <- round(res$coefficients[i, "t value"], 3)
  data[i,5] <- round(res$coefficients[i, "Pr(>|t|)"], 3)
}

# create table
data <- data[, -1]

rownames(data) <- c(trait, "Female", "Age", 
                    "Education: Primary School", "Education: High School", "Education: Vocational", 
                    "Education: Seminary", "Education: Undergraduate", "Education: Graduate", "Education: Phd")


table <- data %>%
  kbl(format = "latex", booktabs = TRUE, align = "cccc",
      col.names = c("Estimate", "Std. error", "t-value", "P(>|t|)")) %>%
  kable_styling()

# fix problem with special latex characters
table <- gsub("P\\([^)]*\\|t\\|\\)", "P(\\\\textgreater{}\\\\textbar{}t\\\\textbar{})", table, perl = TRUE)

# save table
writeLines(table, here("Tables", "Table_A26.tex"))

# print table
table %>% cat()
```



# Table A27: Regression results, kernel-regularized least squares: dominance. Outcome: ballot paper placement

```{r tab_A27_1, eval = FALSE}
# KRLS takes a long time to run. Therefore, we do not run this chunk.
## Instead we load the saved result object in the next chunk and create the 
## table from that.

# load candidate data
load(here("Data", "Candidate_data.RData"))

# remove observations without images, bad quality images, and images with text
df <- df %>% filter(image_missing == 0, image_bad == 0, image_text == 0)

# recode general election dependent variables
fv22 <- df %>% filter(election == "FV22") %>% 
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes)))

# recode local election dependent variables
kv21 <- df %>% filter(election == "KV21") %>%
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes))) 

# recombine data
df <- rbind(fv22, kv21)

# create second data set by removing observation with only one candidate on the ballot
df2 <- df %>% filter(no_candidates_on_ballot != 1)

# create education dummies
df2 <- fastDummies::dummy_cols(df2, select_columns = "education")

df2 <- df2 %>% rename("education_Primary_School" = "education_Primary School",
                    "education_High_School" = "education_High School",
                    "education_Phd" = "education_Ph.d")


# set names

trait <- "Dominance"
trait2 <- "dominance_pd"


d <- df2  %>%
  mutate(gender = as.numeric(gender),
         election_type = ifelse(election_type == "Local election", 1, 0)) %>%
  mutate(election_type = as.numeric(as.character(election_type))) 

d <- d[, c("percentile_rank_std", trait2, "gender", "age",
           "education_Primary_School", "education_High_School", "education_Vocational",
           "education_Seminary", "education_Undergraduate", "education_Graduate", "education_Phd")]
d <- d[complete.cases(d), ]

# run krls - this takes a long time to run
out <- krls(y = d[,1], X = d[,-1], derivative = TRUE, binary = TRUE, print.level = 0)
summary(out)

save(out, file=here("Data", "KRLS.Dominance.Ballot.Position.RData"))
```

```{r tab_A27_2}
# load krls results
load(here("Data", "KRLS.Dominance.Ballot.Position.RData"))
res <- summary(out)

# set names
trait <- "Dominance"
trait2 <- "dominance_pd"



# data frame for results
ame <- c("dominance_pd", "gender", "age", "education_Primary_School*", 
         "education_High_School*", "education_Vocational*", "education_Seminary*",
         "education_Undergraduate*", "education_Graduate*", "education_Phd*")

data <- data.frame(
  ame,
  Est = rep(NA, length(ame)),
  Std_error = rep(NA, length(ame)),
  t_value = rep(NA, length(ame)),
  Pr = rep(NA, length(ame))
)

# add summary results to data table
for(i in 1:10) {
  data[i,2] <- round(res$coefficients[i], 3)
  data[i,3] <- round(res$coefficients[i, "Std. Error"], 3)
  data[i,4] <- round(res$coefficients[i, "t value"], 3)
  data[i,5] <- round(res$coefficients[i, "Pr(>|t|)"], 3)
}

# create table
data <- data[, -1]

# create table
rownames(data) <- c(trait, "Female", "Age", 
                    "Education: Primary School", "Education: High School", "Education: Vocational", 
                    "Education: Seminary", "Education: Undergraduate", "Education: Graduate", "Education: Phd")


table <- data %>%
  kbl(format = "latex", booktabs = TRUE, align = "cccc",
      col.names = c("Estimate", "Std. error", "t-value", "P(>|t|)")) %>%
  kable_styling()

# fix problem with special latex characters
table <- gsub("P\\([^)]*\\|t\\|\\)", "P(\\\\textgreater{}\\\\textbar{}t\\\\textbar{})", table, perl = TRUE)

# save table
writeLines(table, here("Tables", "Table_A27.tex"))

# print table
table %>% cat()
```

# Table A28: Regression results, kernel-regularized least squares: dominance. Outcome: personal votes

```{r tab_A28_1, eval=FALSE}
# KRLS takes a long time to run. Therefore, we do not run this chunk.
## Instead we load the saved result object in the next chunk and create the 
## table from that.

# load candidate data
load(here("Data", "Candidate_data.RData"))

# remove observations without images, bad quality images, and images with text
df <- df %>% filter(image_missing == 0, image_bad == 0, image_text == 0)

# recode general election dependent variables
fv22 <- df %>% filter(election == "FV22") %>% 
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes)))

# recode local election dependent variables
kv21 <- df %>% filter(election == "KV21") %>%
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes))) 

# recombine data
df <- rbind(fv22, kv21)

# create second data set by removing observation with only one candidate on the ballot
df2 <- df %>% filter(no_candidates_on_ballot != 1)

# create education dummies
df <- fastDummies::dummy_cols(df, select_columns = "education")

df <- df %>% rename("education_Primary_School" = "education_Primary School",
                    "education_High_School" = "education_High School",
                    "education_Phd" = "education_Ph.d")

# set names
trait <- "Dominance"
trait2 <- "dominance_pd"

d <- df  %>%
  mutate(gender = as.numeric(gender),
         election_type = ifelse(election_type == "Local election", 1, 0)) %>%
  mutate(election_type = as.numeric(as.character(election_type))) 

d <- d[, c("personal_votes_std", trait2, "gender", "age",
           "education_Primary_School", "education_High_School", "education_Vocational",
           "education_Seminary", "education_Undergraduate", "education_Graduate", "education_Phd")]
d <- d[complete.cases(d), ]

out <- krls(y = d[,1], X = d[,-1], derivative = TRUE, binary = TRUE, print.level = 0)
summary(out)

save(out, file=here("Data", "KRLS.Dominance.Personal.Votes.RData"))
```

```{r tab_A28_2}
# load krls results
load(here("Data", "KRLS.Dominance.Personal.Votes.RData"))
res <- summary(out)

# set names
trait <- "Dominance"
trait2 <- "dominance_pd"

# data frame for results
ame <- c("dominance_pd", "gender", "age", "education_Primary_School*", 
         "education_High_School*", "education_Vocational*", "education_Seminary*",
         "education_Undergraduate*", "education_Graduate*", "education_Phd*")

data <- data.frame(
  ame,
  Est = rep(NA, length(ame)),
  Std_error = rep(NA, length(ame)),
  t_value = rep(NA, length(ame)),
  Pr = rep(NA, length(ame))
)

# add summary results to data table
for(i in 1:10) {
  data[i,2] <- round(res$coefficients[i], 3)
  data[i,3] <- round(res$coefficients[i, "Std. Error"], 3)
  data[i,4] <- round(res$coefficients[i, "t value"], 3)
  data[i,5] <- round(res$coefficients[i, "Pr(>|t|)"], 3)
}

# create table
data <- data[, -1]

rownames(data) <- c(trait, "Female", "Age", 
                    "Education: Primary School", "Education: High School", "Education: Vocational", 
                    "Education: Seminary", "Education: Undergraduate", "Education: Graduate", "Education: Phd")

table <- data %>%
  kbl(format = "latex", booktabs = TRUE, align = "cccc",
      col.names = c("Estimate", "Std. error", "t-value", "P(>|t|)")) %>%
  kable_styling()

# fix problem with special latex characters
table <- gsub("P\\([^)]*\\|t\\|\\)", "P(\\\\textgreater{}\\\\textbar{}t\\\\textbar{})", table, perl = TRUE)

# save table
writeLines(table, here("Tables", "Table_A28.tex"))

# print table
table %>% cat()
```


# Figure A8: Sensitivity analysis (Cinelli and Hazlett 2020): attractiveness, outcome: ballot paper placement

```{r fig_A8}
# load candidate data
load(here("Data", "Candidate_data.RData"))

# remove observations without images, bad quality images, and images with text
df <- df %>% filter(image_missing == 0, image_bad == 0, image_text == 0)

# recode general election dependent variables
fv22 <- df %>% filter(election == "FV22") %>% 
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes)))

# recode local election dependent variables
kv21 <- df %>% filter(election == "KV21") %>%
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes))) 

# recombine data
df <- rbind(fv22, kv21)

# create second data set by removing observation with only one candidate on the ballot
df2 <- df %>% filter(no_candidates_on_ballot != 1)


### Specify models

trait <- "Attractiveness"
trait2 <- "attractiveness_pd"
election <- "Pooled Data"

# Ballot Paper Placement
model1b <- lm(formula = paste0("percentile_rank_std ~ ", trait2, "+ age + gender + education"), data = df2)

ballot.sensitivity <- sensemakr(model = model1b, 
                                treatment = trait2,
                                benchmark_covariates = "gender1",
                                kd = 1:2) 

pdf(file=here("Figures", "Fig_A8.pdf")) 
par(mfrow = c(1, 2))
par(mar = c(0, 3, 0, 3))

plot(ballot.sensitivity, cex.main = 1, main = "a) Point estimate")

plot(ballot.sensitivity, sensitivity.of = "t-value", main = "b) t-value", cex.main = 1)
graphics.off()
```



# Figure A9: Sensitivity analysis (Cinelli and Hazlett 2020): attractiveness, outcome: personal votes

```{r fig_A9}
# load candidate data
load(here("Data", "Candidate_data.RData"))

# remove observations without images, bad quality images, and images with text
df <- df %>% filter(image_missing == 0, image_bad == 0, image_text == 0)

# recode general election dependent variables
fv22 <- df %>% filter(election == "FV22") %>% 
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes)))

# recode local election dependent variables
kv21 <- df %>% filter(election == "KV21") %>%
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes))) 

# recombine data
df <- rbind(fv22, kv21)

# create second data set by removing observation with only one candidate on the ballot
df2 <- df %>% filter(no_candidates_on_ballot != 1)


### Specify models

trait <- "Attractiveness"
trait2 <- "attractiveness_pd"
election <- "Pooled Data"

# Personal votes
model2b <- lm(formula = paste0("personal_votes_std ~ ", trait2, "+ age + gender + education"), data = df)

votes.sensitivity <- sensemakr(model = model2b, 
                               treatment = trait2,
                               benchmark_covariates = "gender1",
                               kd = 1:2) 
pdf(file=here("Figures", "Fig_A9.pdf")) 
par(mfrow = c(1, 2))
par(mar = c(0, 3, 0, 3))

plot(votes.sensitivity, cex.main = 1, main = "a) Point estimate")

plot(votes.sensitivity, sensitivity.of = "t-value", main = "b) t-value", cex.main = 1)
graphics.off()
```



# Figure A10: Sensitivity analysis (Cinelli and Hazlett 2020): trustworthiness, outcome: personal votes.

```{r fig_A10}
# load candidate data
load(here("Data", "Candidate_data.RData"))

# remove observations without images, bad quality images, and images with text
df <- df %>% filter(image_missing == 0, image_bad == 0, image_text == 0)

# recode general election dependent variables
fv22 <- df %>% filter(election == "FV22") %>% 
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes)))

# recode local election dependent variables
kv21 <- df %>% filter(election == "KV21") %>%
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes))) 

# recombine data
df <- rbind(fv22, kv21)

# create second data set by removing observation with only one candidate on the ballot
df2 <- df %>% filter(no_candidates_on_ballot != 1)

trait <- "Trustworthiness"
trait2 <- "trustworthiness_pd"
election <- "Pooled Data"


# personal votes 
model2b <- lm(formula = paste0("personal_votes_std ~ ", trait2, "+ age + gender + education"), data = df)

votes.sensitivity <- sensemakr(model = model2b, 
                               treatment = trait2,
                               benchmark_covariates = "gender1",
                               kd = 1:2) 
pdf(file=here("Figures", "Fig_10.pdf")) 
par(mfrow = c(1, 2))
par(mar = c(0, 3, 0, 3))

plot(votes.sensitivity, cex.main = 1, main = "a) Point estimate")

plot(votes.sensitivity, sensitivity.of = "t-value", main = "b) t-value", cex.main = 1)

graphics.off()
```



# Figure A11: Sensitivity analysis (Cinelli and Hazlett 2020): dominance, outcome: personal votes

```{r fig_A11}
# load candidate data
load(here("Data", "Candidate_data.RData"))

# remove observations without images, bad quality images, and images with text
df <- df %>% filter(image_missing == 0, image_bad == 0, image_text == 0)

# recode general election dependent variables
fv22 <- df %>% filter(election == "FV22") %>% 
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes)))

# recode local election dependent variables
kv21 <- df %>% filter(election == "KV21") %>%
  mutate(percentile_rank_std = scale(percentile_rank),
         personal_votes_std = scale(log(personal_votes))) 

# recombine data
df <- rbind(fv22, kv21)

# create second data set by removing observation with only one candidate on the ballot
df2 <- df %>% filter(no_candidates_on_ballot != 1)


trait <- "Dominance"
trait2 <- "dominance_pd"
election <- "Pooled Data"


# Personal votes
model2b <- lm(formula = paste0("personal_votes_std ~ ", trait2, "+ age + gender + education"), data = df)

votes.sensitivity <- sensemakr(model = model2b, 
                               treatment = trait2,
                               benchmark_covariates = "gender1",
                               kd = 1:1) 

# save file
pdf(file=here("Figures", "Fig_A11.pdf")) 
par(mfrow = c(1, 2))
par(mar = c(0, 3, 0, 3))

plot(votes.sensitivity, cex.main = 1, main = "a) Point estimate")

plot(votes.sensitivity, sensitivity.of = "t-value", main = "b) t-value", cex.main = 1)
graphics.off()
```



